From 2c1d1b75b73e0ab941dc8f2bd2e280cdf2ee75f4 Mon Sep 17 00:00:00 2001 From: Daniel <63580393+danrgll@users.noreply.github.com> Date: Wed, 29 May 2024 13:36:31 +0200 Subject: [PATCH 01/29] add mixed usage functionality for run_args and neps func arguments --- neps/api.py | 58 ++++++++++++++------------------ neps/utils/run_args_from_yaml.py | 30 ++++++++++------- 2 files changed, 43 insertions(+), 45 deletions(-) diff --git a/neps/api.py b/neps/api.py index 664c163f..1b7f2875 100644 --- a/neps/api.py +++ b/neps/api.py @@ -10,7 +10,7 @@ import ConfigSpace as CS from neps.utils.run_args_from_yaml import check_essential_arguments, get_run_args_from_yaml,\ - check_arg_defaults + check_double_reference from neps.utils.common import instance_from_map from neps.runtime import launch_runtime @@ -221,46 +221,38 @@ def run( del searcher_kwargs["budget"] logger = logging.getLogger("neps") - # if arguments via run_args provided overwrite them if run_args: - # Check if the user provided other arguments directly to neps.run(). - # If so, raise an error. - check_arg_defaults(run, locals()) - - # Warning if the user has specified default values for arguments that differ - # from those specified in 'run_args'. These user-defined changes are not applied. - warnings.warn( - "WARNING: Loading arguments from 'run_args'. Arguments directly provided " - "to neps.run(...) will be not used!" - ) optim_settings = get_run_args_from_yaml(run_args) + check_double_reference(run, locals(), optim_settings) - # Update each argument based on optim_settings. If not key is not provided in yaml - # use default value. Currently strict but will change in the future. - run_pipeline = optim_settings.get("run_pipeline", None) - root_directory = optim_settings.get("root_directory", None) - pipeline_space = optim_settings.get("pipeline_space", None) + run_pipeline = optim_settings.get("run_pipeline", run_pipeline) + root_directory = optim_settings.get("root_directory", root_directory) + pipeline_space = optim_settings.get("pipeline_space", pipeline_space) overwrite_working_directory = optim_settings.get( - "overwrite_working_directory", False + "overwrite_working_directory", overwrite_working_directory ) - post_run_summary = optim_settings.get("post_run_summary", False) - development_stage_id = optim_settings.get("development_stage_id", None) - task_id = optim_settings.get("task_id", None) - max_evaluations_total = optim_settings.get("max_evaluations_total", None) - max_evaluations_per_run = optim_settings.get("max_evaluations_per_run", None) + post_run_summary = optim_settings.get("post_run_summary", post_run_summary) + development_stage_id = optim_settings.get("development_stage_id", + development_stage_id) + task_id = optim_settings.get("task_id", task_id) + max_evaluations_total = optim_settings.get("max_evaluations_total", + max_evaluations_total) + max_evaluations_per_run = optim_settings.get("max_evaluations_per_run", + max_evaluations_per_run) continue_until_max_evaluation_completed = optim_settings.get( "continue_until_max_evaluation_completed", - False, - ) - max_cost_total = optim_settings.get("max_cost_total", None) - ignore_errors = optim_settings.get("ignore_errors", False) - loss_value_on_error = optim_settings.get("loss_value_on_error", None) - cost_value_on_error = optim_settings.get("cost_value_on_error", None) - pre_load_hooks = optim_settings.get("pre_load_hooks", None) - searcher = optim_settings.get("searcher", "default") - searcher_path = optim_settings.get("searcher_path", None) - for key, value in optim_settings.get("searcher_kwargs", {}).items(): + continue_until_max_evaluation_completed) + max_cost_total = optim_settings.get("max_cost_total", max_cost_total) + ignore_errors = optim_settings.get("ignore_errors", ignore_errors) + loss_value_on_error = optim_settings.get("loss_value_on_error", + loss_value_on_error) + cost_value_on_error = optim_settings.get("cost_value_on_error", + cost_value_on_error) + pre_load_hooks = optim_settings.get("pre_load_hooks", pre_load_hooks) + searcher = optim_settings.get("searcher", searcher) + searcher_path = optim_settings.get("searcher_path", searcher_path) + for key, value in optim_settings.get("searcher_kwargs", searcher_kwargs).items(): searcher_kwargs[key] = value # check if necessary arguments are provided. diff --git a/neps/utils/run_args_from_yaml.py b/neps/utils/run_args_from_yaml.py index 1f290423..0fcb5039 100644 --- a/neps/utils/run_args_from_yaml.py +++ b/neps/utils/run_args_from_yaml.py @@ -468,6 +468,7 @@ def check_essential_arguments( if not root_directory: raise ValueError("'root_directory' is required but was not provided.") if not pipeline_space: + # ToDO: irg was falsch # handling special case for searcher instance, in which user doesn't have to # provide the search_space because it's the argument of the searcher. if run_args or not isinstance(searcher, BaseOptimizer): @@ -480,29 +481,34 @@ def check_essential_arguments( ) -def check_arg_defaults(func: Callable, provided_arguments: Dict) -> Any: +def check_double_reference(func: Callable, func_arguments: Dict, yaml_arguments: Dict) -> Any: """ - Checks if provided arguments deviate from default values defined in the function's - signature. + Checks if no argument is defined both via function arguments and YAML. Parameters: - func (Callable): The function to check arguments against. - - provided_arguments: A dictionary containing the provided arguments and their - values. + - func_arguments (Dict): A dictionary containing the provided arguments to the + function and their values. + - yaml_arguments (Dict): A dictionary containing the arguments provided via a YAML + file. Raises: - - ValueError: If any provided argument differs from its default value in the function - signature. + - ValueError: If any provided argument is defined both via function arguments and the + YAML file. """ sig = inspect.signature(func) + for name, param in sig.parameters.items(): - if param.default != provided_arguments[name]: + if param.default != func_arguments[name]: if name == RUN_ARGS: - # ignoring run_args argument + # Ignoring run_args argument continue if name == SEARCHER_KWARGS: # searcher_kwargs does not have a default specified by inspect - if provided_arguments[name] == {}: + if func_arguments[name] == {}: continue - raise ValueError( - f"Argument '{name}' must not be set directly when 'run_args' is used.") + if name in yaml_arguments: + raise ValueError( + f"Conflict for argument '{name}': Argument is defined both via " + f"function arguments and YAML, which is not allowed." + ) From f7373d93432cf85072c005ce38650105bf47b4e0 Mon Sep 17 00:00:00 2001 From: Daniel <63580393+danrgll@users.noreply.github.com> Date: Wed, 29 May 2024 14:55:46 +0200 Subject: [PATCH 02/29] align the tests for declarative usage to the ones that are in the docs + try fix test --- .../full_configuration_template.yaml | 2 +- neps/api.py | 1 - .../customizing_neps_optimizer.yaml | 6 +--- .../defining_hooks.yaml | 8 +++-- .../full_configuration_template.yaml | 14 +++++--- .../loading_own_optimizer.yaml | 2 +- .../loading_pipeline_space_dict.yaml | 6 ++-- .../test_declarative_usage_docs/neps_run.py | 21 ------------ .../outsourcing_optimizer.yaml | 4 +-- .../outsourcing_pipeline_space.yaml | 3 +- .../pipeline_space.yaml | 16 +++++++++- .../run_pipeline.py | 10 ++++++ .../simple_example.yaml | 8 +++-- ...simple_example_including_run_pipeline.yaml | 12 ++++--- .../test_declarative_usage_docs.py | 32 +++++++++++++++---- 15 files changed, 89 insertions(+), 56 deletions(-) delete mode 100644 tests/test_yaml_run_args/test_declarative_usage_docs/neps_run.py diff --git a/docs/doc_yamls/full_configuration_template.yaml b/docs/doc_yamls/full_configuration_template.yaml index c68b7570..6e9ae302 100644 --- a/docs/doc_yamls/full_configuration_template.yaml +++ b/docs/doc_yamls/full_configuration_template.yaml @@ -36,7 +36,7 @@ cost_value_on_error: ignore_errors: # Customization Options -searcher: bayesian_optimization # Internal key to select a NePS optimizer. +searcher: hyperband # Internal key to select a NePS optimizer. # Hooks pre_load_hooks: diff --git a/neps/api.py b/neps/api.py index 1b7f2875..bb76f50c 100644 --- a/neps/api.py +++ b/neps/api.py @@ -222,7 +222,6 @@ def run( logger = logging.getLogger("neps") if run_args: - optim_settings = get_run_args_from_yaml(run_args) check_double_reference(run, locals(), optim_settings) diff --git a/tests/test_yaml_run_args/test_declarative_usage_docs/customizing_neps_optimizer.yaml b/tests/test_yaml_run_args/test_declarative_usage_docs/customizing_neps_optimizer.yaml index da1f4ca5..b079193e 100644 --- a/tests/test_yaml_run_args/test_declarative_usage_docs/customizing_neps_optimizer.yaml +++ b/tests/test_yaml_run_args/test_declarative_usage_docs/customizing_neps_optimizer.yaml @@ -1,6 +1,6 @@ run_pipeline: path: path/to/your/run_pipeline.py # Path to the function file - name: example_run # Function name within the file + name: example_pipeline # Function name within the file pipeline_space: learning_rate: @@ -19,10 +19,6 @@ searcher: initial_design_size: 7 surrogate_model: gp acquisition: EI - log_prior_weighted: false acquisition_sampler: random random_interleave_prob: 0.1 - disable_priors: false - prior_confidence: high - sample_default_first: false diff --git a/tests/test_yaml_run_args/test_declarative_usage_docs/defining_hooks.yaml b/tests/test_yaml_run_args/test_declarative_usage_docs/defining_hooks.yaml index d792f7d7..186acaab 100644 --- a/tests/test_yaml_run_args/test_declarative_usage_docs/defining_hooks.yaml +++ b/tests/test_yaml_run_args/test_declarative_usage_docs/defining_hooks.yaml @@ -1,16 +1,20 @@ # Basic NEPS Configuration Example run_pipeline: path: path/to/your/run_pipeline.py # Path to the function file - name: example_run # Function name within the file + name: example_pipeline # Function name within the file pipeline_space: learning_rate: lower: 1e-5 upper: 1e-1 log: True # Log scale for learning rate + epochs: + lower: 5 + upper: 20 + is_fidelity: True optimizer: choices: [adam, sgd, adamw] - epochs: 50 + batch_size: 64 root_directory: path/to/results # Directory for result storage max_evaluations_total: 20 # Budget diff --git a/tests/test_yaml_run_args/test_declarative_usage_docs/full_configuration_template.yaml b/tests/test_yaml_run_args/test_declarative_usage_docs/full_configuration_template.yaml index eee0b0fe..5747c058 100644 --- a/tests/test_yaml_run_args/test_declarative_usage_docs/full_configuration_template.yaml +++ b/tests/test_yaml_run_args/test_declarative_usage_docs/full_configuration_template.yaml @@ -1,16 +1,20 @@ # Full Configuration Template for NePS run_pipeline: - path: tests/test_yaml_run_args/test_declarative_usage_docs/run_pipeline.py # Path to the Python file that contains your run_pipeline - name: run_pipeline # Name of your run_pipeline function within this file + path: tests/test_yaml_run_args/test_declarative_usage_docs/run_pipeline.py + name: run_pipeline_constant pipeline_space: learning_rate: lower: 1e-5 upper: 1e-1 - log: True + log: True # Log scale for learning rate + epochs: + lower: 5 + upper: 20 + is_fidelity: True optimizer: choices: [adam, sgd, adamw] - epochs: 50 + batch_size: 64 root_directory: path/to/results # Directory for result storage max_evaluations_total: 20 # Budget @@ -32,7 +36,7 @@ cost_value_on_error: ignore_errors: # Customization Options -searcher: bayesian_optimization # Internal key to select a NePS optimizer. +searcher: hyperband # Internal key to select a NePS optimizer. # Hooks pre_load_hooks: diff --git a/tests/test_yaml_run_args/test_declarative_usage_docs/loading_own_optimizer.yaml b/tests/test_yaml_run_args/test_declarative_usage_docs/loading_own_optimizer.yaml index 6174cd38..26897062 100644 --- a/tests/test_yaml_run_args/test_declarative_usage_docs/loading_own_optimizer.yaml +++ b/tests/test_yaml_run_args/test_declarative_usage_docs/loading_own_optimizer.yaml @@ -1,6 +1,6 @@ run_pipeline: path: path/to/your/run_pipeline.py # Path to the function file - name: example_run # Function name within the file + name: example_pipeline # Function name within the file pipeline_space: learning_rate: diff --git a/tests/test_yaml_run_args/test_declarative_usage_docs/loading_pipeline_space_dict.yaml b/tests/test_yaml_run_args/test_declarative_usage_docs/loading_pipeline_space_dict.yaml index f5f532e2..a8185c79 100644 --- a/tests/test_yaml_run_args/test_declarative_usage_docs/loading_pipeline_space_dict.yaml +++ b/tests/test_yaml_run_args/test_declarative_usage_docs/loading_pipeline_space_dict.yaml @@ -1,11 +1,11 @@ -# Basic NEPS Configuration Example +# Loading pipeline space from a python dict run_pipeline: path: path/to/your/run_pipeline.py # Path to the function file - name: example_run # Function name within the file + name: example_pipeline # Function name within the file pipeline_space: path: path/to/your/search_space.py # Path to the dict file - name: search_space # Name of the dict instance + name: pipeline_space # Name of the dict instance root_directory: path/to/results # Directory for result storage max_evaluations_total: 20 # Budget diff --git a/tests/test_yaml_run_args/test_declarative_usage_docs/neps_run.py b/tests/test_yaml_run_args/test_declarative_usage_docs/neps_run.py deleted file mode 100644 index 7829225d..00000000 --- a/tests/test_yaml_run_args/test_declarative_usage_docs/neps_run.py +++ /dev/null @@ -1,21 +0,0 @@ -import argparse -import numpy as np -import neps - - -def run_pipeline(learning_rate, optimizer, epochs): - """func for test loading of run_pipeline""" - if optimizer == "a": - eval_score = np.random.choice([learning_rate, epochs], 1) - else: - eval_score = 5.0 - return {"loss": eval_score} - - -if __name__ == "__main__": - parser = argparse.ArgumentParser( - description="Run NEPS optimization with run_args.yml." - ) - parser.add_argument("run_args", type=str, help="Path to the YAML configuration file.") - args = parser.parse_args() - neps.run(run_args=args.run_args) diff --git a/tests/test_yaml_run_args/test_declarative_usage_docs/outsourcing_optimizer.yaml b/tests/test_yaml_run_args/test_declarative_usage_docs/outsourcing_optimizer.yaml index 17bd4ab4..e6a3cea5 100644 --- a/tests/test_yaml_run_args/test_declarative_usage_docs/outsourcing_optimizer.yaml +++ b/tests/test_yaml_run_args/test_declarative_usage_docs/outsourcing_optimizer.yaml @@ -1,7 +1,7 @@ -# Basic NEPS Configuration Example +# Optimizer settings from YAML configuration run_pipeline: path: path/to/your/run_pipeline.py # Path to the function file - name: example_run # Function name within the file + name: example_pipeline # Function name within the file pipeline_space: learning_rate: diff --git a/tests/test_yaml_run_args/test_declarative_usage_docs/outsourcing_pipeline_space.yaml b/tests/test_yaml_run_args/test_declarative_usage_docs/outsourcing_pipeline_space.yaml index 75e50c30..68558569 100644 --- a/tests/test_yaml_run_args/test_declarative_usage_docs/outsourcing_pipeline_space.yaml +++ b/tests/test_yaml_run_args/test_declarative_usage_docs/outsourcing_pipeline_space.yaml @@ -1,6 +1,7 @@ +# Pipeline space settings from YAML run_pipeline: path: path/to/your/run_pipeline.py # Path to the function file - name: example_run # Function name within the file + name: example_pipeline # Function name within the file pipeline_space: path/to/your/pipeline_space.yaml diff --git a/tests/test_yaml_run_args/test_declarative_usage_docs/pipeline_space.yaml b/tests/test_yaml_run_args/test_declarative_usage_docs/pipeline_space.yaml index 5f416fbd..a4782db5 100644 --- a/tests/test_yaml_run_args/test_declarative_usage_docs/pipeline_space.yaml +++ b/tests/test_yaml_run_args/test_declarative_usage_docs/pipeline_space.yaml @@ -1,8 +1,22 @@ +# pipeline_space including priors and fidelity pipeline_space: learning_rate: lower: 1e-5 upper: 1e-1 log: True # Log scale for learning rate + default: 1e-2 + default_confidence: "medium" + epochs: + lower: 5 + upper: 20 + is_fidelity: True + dropout_rate: + lower: 0.1 + upper: 0.5 + default: 0.2 + default_confidence: "high" optimizer: choices: [adam, sgd, adamw] - epochs: 50 + default: adam + # default confidence low + batch_size: 64 diff --git a/tests/test_yaml_run_args/test_declarative_usage_docs/run_pipeline.py b/tests/test_yaml_run_args/test_declarative_usage_docs/run_pipeline.py index b0b26c3d..f44e6e71 100644 --- a/tests/test_yaml_run_args/test_declarative_usage_docs/run_pipeline.py +++ b/tests/test_yaml_run_args/test_declarative_usage_docs/run_pipeline.py @@ -9,3 +9,13 @@ def run_pipeline(learning_rate, optimizer, epochs): eval_score = 5.0 return {"loss": eval_score} + +def run_pipeline_constant(learning_rate, optimizer, epochs, batch_size): + """func for test loading of run_pipeline""" + if optimizer == "a": + eval_score = np.random.choice([learning_rate, epochs], 1) + else: + eval_score = 5.0 + eval_score += batch_size + return {"loss": eval_score} + diff --git a/tests/test_yaml_run_args/test_declarative_usage_docs/simple_example.yaml b/tests/test_yaml_run_args/test_declarative_usage_docs/simple_example.yaml index 77ceccb5..5c4758a2 100644 --- a/tests/test_yaml_run_args/test_declarative_usage_docs/simple_example.yaml +++ b/tests/test_yaml_run_args/test_declarative_usage_docs/simple_example.yaml @@ -3,10 +3,14 @@ pipeline_space: learning_rate: lower: 1e-5 upper: 1e-1 - log: True + log: True # Log scale for learning rate + epochs: + lower: 5 + upper: 20 + is_fidelity: True optimizer: choices: [adam, sgd, adamw] - epochs: 50 + batch_size: 64 root_directory: path/to/results # Directory for result storage max_evaluations_total: 20 # Budget diff --git a/tests/test_yaml_run_args/test_declarative_usage_docs/simple_example_including_run_pipeline.yaml b/tests/test_yaml_run_args/test_declarative_usage_docs/simple_example_including_run_pipeline.yaml index d46f95f4..6d535e62 100644 --- a/tests/test_yaml_run_args/test_declarative_usage_docs/simple_example_including_run_pipeline.yaml +++ b/tests/test_yaml_run_args/test_declarative_usage_docs/simple_example_including_run_pipeline.yaml @@ -1,16 +1,20 @@ -# Extended NePS Configuration Example with External Pipeline Function +# Simple NePS configuration including run_pipeline run_pipeline: path: tests/test_yaml_run_args/test_declarative_usage_docs/run_pipeline.py - name: run_pipeline + name: run_pipeline_constant pipeline_space: learning_rate: lower: 1e-5 upper: 1e-1 - log: True + log: True # Log scale for learning rate + epochs: + lower: 5 + upper: 20 + is_fidelity: True optimizer: choices: [adam, sgd, adamw] - epochs: 50 + batch_size: 64 root_directory: path/to/results # Directory for result storage max_evaluations_total: 20 # Budget diff --git a/tests/test_yaml_run_args/test_declarative_usage_docs/test_declarative_usage_docs.py b/tests/test_yaml_run_args/test_declarative_usage_docs/test_declarative_usage_docs.py index c8459b6c..e0431473 100644 --- a/tests/test_yaml_run_args/test_declarative_usage_docs/test_declarative_usage_docs.py +++ b/tests/test_yaml_run_args/test_declarative_usage_docs/test_declarative_usage_docs.py @@ -1,7 +1,7 @@ import pytest -import subprocess import os -import sys +import neps +from run_pipeline import run_pipeline_constant BASE_PATH = "tests/test_yaml_run_args/test_declarative_usage_docs/" @@ -13,10 +13,28 @@ def test_run_with_yaml(yaml_file: str) -> None: """Test "neps.run" with various run_args.yaml settings to simulate loading options for variables.""" - assert os.path.exists(BASE_PATH + yaml_file), f"{yaml_file} does not exist." + yaml_path = os.path.join(BASE_PATH, yaml_file) + assert os.path.exists(yaml_path), f"{yaml_file} does not exist." try: - subprocess.check_call([sys.executable, BASE_PATH + 'neps_run.py', BASE_PATH + - yaml_file]) - except subprocess.CalledProcessError: - pytest.fail(f"NePS run failed for configuration: {yaml_file}") + neps.run(run_args=yaml_path) + except Exception as e: + pytest.fail(f"NePS run failed for configuration: {yaml_file} with error: {str(e)}" + ) + + +@pytest.mark.neps_api +def test_run_with_yaml_and_run_pipeline() -> None: + """Test "neps.run" with simple_example.yaml as run_args + a run_pipeline that is + provided separately""" + yaml_path = os.path.join(BASE_PATH, "simple_example.yaml") + assert os.path.exists(yaml_path), f"{yaml_path} does not exist." + + try: + neps.run(run_args=yaml_path, run_pipeline=run_pipeline_constant) + except Exception as e: + pytest.fail(f"NePS run failed for configuration: {yaml_path} with error: {str(e)}" + ) + + + From 68ed70203ccc3ae27348a5eba61674cedb38f245 Mon Sep 17 00:00:00 2001 From: Daniel <63580393+danrgll@users.noreply.github.com> Date: Wed, 29 May 2024 22:59:52 +0200 Subject: [PATCH 03/29] fix errors in tests --- .../full_configuration_template.yaml | 2 +- .../full_configuration_template.yaml | 4 +- .../test_declarative_usage_docs/neps_run.py | 19 ++++++++++ .../simple_example.yaml | 2 +- ...simple_example_including_run_pipeline.yaml | 2 +- .../test_declarative_usage_docs.py | 38 ++++++++++--------- .../test_yaml_run_args/test_yaml_run_args.py | 1 - 7 files changed, 45 insertions(+), 23 deletions(-) create mode 100644 tests/test_yaml_run_args/test_declarative_usage_docs/neps_run.py diff --git a/docs/doc_yamls/full_configuration_template.yaml b/docs/doc_yamls/full_configuration_template.yaml index 6e9ae302..ca6abf1a 100644 --- a/docs/doc_yamls/full_configuration_template.yaml +++ b/docs/doc_yamls/full_configuration_template.yaml @@ -22,7 +22,7 @@ max_cost_total: # Debug and Monitoring overwrite_working_directory: True -post_run_summary: True +post_run_summary: False development_stage_id: task_id: diff --git a/tests/test_yaml_run_args/test_declarative_usage_docs/full_configuration_template.yaml b/tests/test_yaml_run_args/test_declarative_usage_docs/full_configuration_template.yaml index 5747c058..b79616a4 100644 --- a/tests/test_yaml_run_args/test_declarative_usage_docs/full_configuration_template.yaml +++ b/tests/test_yaml_run_args/test_declarative_usage_docs/full_configuration_template.yaml @@ -16,13 +16,13 @@ pipeline_space: choices: [adam, sgd, adamw] batch_size: 64 -root_directory: path/to/results # Directory for result storage +root_directory: path/to/results/full_config # Directory for result storage max_evaluations_total: 20 # Budget max_cost_total: # Debug and Monitoring overwrite_working_directory: True -post_run_summary: True +post_run_summary: False development_stage_id: task_id: diff --git a/tests/test_yaml_run_args/test_declarative_usage_docs/neps_run.py b/tests/test_yaml_run_args/test_declarative_usage_docs/neps_run.py new file mode 100644 index 00000000..16533af6 --- /dev/null +++ b/tests/test_yaml_run_args/test_declarative_usage_docs/neps_run.py @@ -0,0 +1,19 @@ +import argparse +import neps +from tests.test_yaml_run_args.test_declarative_usage_docs.run_pipeline import \ + run_pipeline_constant + + +if __name__ == "__main__": + parser = argparse.ArgumentParser( + description="Run NEPS optimization with run_args.yml." + ) + parser.add_argument("run_args", type=str, help="Path to the YAML configuration file.") + parser.add_argument("--run_pipeline", action="store_true") + args = parser.parse_args() + + if args.run_pipeline: + neps.run(run_args=args.run_args, run_pipeline=run_pipeline_constant) + else: + neps.run(run_args=args.run_args) + diff --git a/tests/test_yaml_run_args/test_declarative_usage_docs/simple_example.yaml b/tests/test_yaml_run_args/test_declarative_usage_docs/simple_example.yaml index 5c4758a2..d50419fe 100644 --- a/tests/test_yaml_run_args/test_declarative_usage_docs/simple_example.yaml +++ b/tests/test_yaml_run_args/test_declarative_usage_docs/simple_example.yaml @@ -12,5 +12,5 @@ pipeline_space: choices: [adam, sgd, adamw] batch_size: 64 -root_directory: path/to/results # Directory for result storage +root_directory: path/to/results/simple_example # Directory for result storage max_evaluations_total: 20 # Budget diff --git a/tests/test_yaml_run_args/test_declarative_usage_docs/simple_example_including_run_pipeline.yaml b/tests/test_yaml_run_args/test_declarative_usage_docs/simple_example_including_run_pipeline.yaml index 6d535e62..6968acd7 100644 --- a/tests/test_yaml_run_args/test_declarative_usage_docs/simple_example_including_run_pipeline.yaml +++ b/tests/test_yaml_run_args/test_declarative_usage_docs/simple_example_including_run_pipeline.yaml @@ -16,5 +16,5 @@ pipeline_space: choices: [adam, sgd, adamw] batch_size: 64 -root_directory: path/to/results # Directory for result storage +root_directory: path/to/results/simple_example_including_run_pipeline # Directory for result storage max_evaluations_total: 20 # Budget diff --git a/tests/test_yaml_run_args/test_declarative_usage_docs/test_declarative_usage_docs.py b/tests/test_yaml_run_args/test_declarative_usage_docs/test_declarative_usage_docs.py index e0431473..d5f39082 100644 --- a/tests/test_yaml_run_args/test_declarative_usage_docs/test_declarative_usage_docs.py +++ b/tests/test_yaml_run_args/test_declarative_usage_docs/test_declarative_usage_docs.py @@ -1,7 +1,7 @@ import pytest import os -import neps -from run_pipeline import run_pipeline_constant +import subprocess +import sys BASE_PATH = "tests/test_yaml_run_args/test_declarative_usage_docs/" @@ -11,30 +11,34 @@ "full_configuration_template.yaml" ]) def test_run_with_yaml(yaml_file: str) -> None: - """Test "neps.run" with various run_args.yaml settings to simulate loading options - for variables.""" + """ + Test 'neps.run' with various run_args.yaml settings to simulate loading options + for variables. + """ yaml_path = os.path.join(BASE_PATH, yaml_file) assert os.path.exists(yaml_path), f"{yaml_file} does not exist." try: - neps.run(run_args=yaml_path) - except Exception as e: - pytest.fail(f"NePS run failed for configuration: {yaml_file} with error: {str(e)}" - ) + subprocess.check_call( + [sys.executable, BASE_PATH + 'neps_run.py', yaml_path]) + except subprocess.CalledProcessError as e: + pytest.fail( + f"NePS run failed for configuration: {yaml_file} with error: {str(e)}") @pytest.mark.neps_api def test_run_with_yaml_and_run_pipeline() -> None: - """Test "neps.run" with simple_example.yaml as run_args + a run_pipeline that is - provided separately""" + """ + Test 'neps.run' with simple_example.yaml as run_args + a run_pipeline that is + provided separately. + """ yaml_path = os.path.join(BASE_PATH, "simple_example.yaml") assert os.path.exists(yaml_path), f"{yaml_path} does not exist." try: - neps.run(run_args=yaml_path, run_pipeline=run_pipeline_constant) - except Exception as e: - pytest.fail(f"NePS run failed for configuration: {yaml_path} with error: {str(e)}" - ) - - - + subprocess.check_call( + [sys.executable, BASE_PATH + 'neps_run.py', yaml_path, "--run_pipeline"] + ) + except subprocess.CalledProcessError as e: + pytest.fail( + f"NePS run failed for configuration: simple_example.yaml with error: {str(e)}") diff --git a/tests/test_yaml_run_args/test_yaml_run_args.py b/tests/test_yaml_run_args/test_yaml_run_args.py index c9d3901e..6ebb7ddc 100644 --- a/tests/test_yaml_run_args/test_yaml_run_args.py +++ b/tests/test_yaml_run_args/test_yaml_run_args.py @@ -12,7 +12,6 @@ batch_size=neps.ConstantParameter(value=64)) - def run_pipeline(): """func to test loading of run_pipeline""" return From bf023e9ac81f64178e1dd51715a33111c98844b5 Mon Sep 17 00:00:00 2001 From: Daniel <63580393+danrgll@users.noreply.github.com> Date: Tue, 4 Jun 2024 19:22:50 +0200 Subject: [PATCH 04/29] change design for pre-load-hooks --- neps/utils/run_args_from_yaml.py | 26 +++++-------------- tests/test_yaml_run_args/run_args_full.yaml | 8 ++---- .../run_args_full_same_level.yaml | 4 +-- .../run_args_invalid_key.yaml | 8 ++---- .../run_args_key_missing.yaml | 4 +-- .../run_args_optional_loading_format.yaml | 4 +-- .../run_args_wrong_name.yaml | 8 ++---- .../run_args_wrong_path.yaml | 8 ++---- .../defining_hooks.yaml | 11 ++++---- .../test_declarative_usage_docs/hooks.py | 8 ++++++ .../simple_example.yaml | 3 +++ ...simple_example_including_run_pipeline.yaml | 2 ++ .../test_declarative_usage_docs.py | 3 ++- 13 files changed, 38 insertions(+), 59 deletions(-) create mode 100644 tests/test_yaml_run_args/test_declarative_usage_docs/hooks.py diff --git a/neps/utils/run_args_from_yaml.py b/neps/utils/run_args_from_yaml.py index 0fcb5039..e95b331e 100644 --- a/neps/utils/run_args_from_yaml.py +++ b/neps/utils/run_args_from_yaml.py @@ -346,32 +346,18 @@ def import_object(path): def load_hooks_from_config(pre_load_hooks_dict: Dict) -> List: """ - Loads hook functions from configurations. - - Iterates through a dictionary of pre-load hooks, dynamically imports each - specified function by its 'path' and 'name', and accumulates the loaded - functions into a list. + Loads hook functions from a dictionary of configurations. Args: - pre_load_hooks_dict (dict): Dictionary of hook configurations, each - containing 'path' and 'name' keys for the hook function. + pre_load_hooks_dict (Dict): Dictionary with hook names as keys and paths as values Returns: - list: List of loaded hook functions. - - Raises: - KeyError: If any hook configuration lacks 'path' or 'name' keys. + List: List of loaded hook functions. """ loaded_hooks = [] - for hook_config in pre_load_hooks_dict.values(): - if "path" in hook_config and "name" in hook_config: - hook_func = load_and_return_object(hook_config["path"], hook_config[ - "name"], PRE_LOAD_HOOKS) - loaded_hooks.append(hook_func) - else: - raise KeyError( - f"Expected keys 'path' and 'name' for hook, but got: " f"{hook_config}." - ) + for name, path in pre_load_hooks_dict.items(): + hook_func = load_and_return_object(path, name, PRE_LOAD_HOOKS) + loaded_hooks.append(hook_func) return loaded_hooks diff --git a/tests/test_yaml_run_args/run_args_full.yaml b/tests/test_yaml_run_args/run_args_full.yaml index 4c87974c..e9dc08eb 100644 --- a/tests/test_yaml_run_args/run_args_full.yaml +++ b/tests/test_yaml_run_args/run_args_full.yaml @@ -31,9 +31,5 @@ search: surrogate_model: gp pre_load_hooks: - hook1: - path: "tests/test_yaml_run_args/test_yaml_run_args.py" - name: hook1 - hook2: - path: "tests/test_yaml_run_args/test_yaml_run_args.py" - name: hook2 + hook1: "tests/test_yaml_run_args/test_yaml_run_args.py" + hook2: "tests/test_yaml_run_args/test_yaml_run_args.py" diff --git a/tests/test_yaml_run_args/run_args_full_same_level.yaml b/tests/test_yaml_run_args/run_args_full_same_level.yaml index e621c892..9e32b64d 100644 --- a/tests/test_yaml_run_args/run_args_full_same_level.yaml +++ b/tests/test_yaml_run_args/run_args_full_same_level.yaml @@ -20,6 +20,4 @@ searcher_kwargs: initial_design_size: 5 surrogate_model: gp pre_load_hooks: - hook1: - path: "tests/test_yaml_run_args/test_yaml_run_args" # check if without .py also works - name: hook1 + hook1: "tests/test_yaml_run_args/test_yaml_run_args" # check if without .py also works diff --git a/tests/test_yaml_run_args/run_args_invalid_key.yaml b/tests/test_yaml_run_args/run_args_invalid_key.yaml index b478abb1..6fc840ce 100644 --- a/tests/test_yaml_run_args/run_args_invalid_key.yaml +++ b/tests/test_yaml_run_args/run_args_invalid_key.yaml @@ -31,9 +31,5 @@ search: surrogate_model: gp pre_load_hooks: - hook1: - path: "tests/test_yaml_run_args/test_yaml_run_args.py" - name: hook1 - hook2: - path: "tests/test_yaml_run_args/test_yaml_run_args.py" - name: hook2 + hook1: "tests/test_yaml_run_args/test_yaml_run_args.py" + hook2: "tests/test_yaml_run_args/test_yaml_run_args.py" diff --git a/tests/test_yaml_run_args/run_args_key_missing.yaml b/tests/test_yaml_run_args/run_args_key_missing.yaml index db3ca836..704d1a2e 100644 --- a/tests/test_yaml_run_args/run_args_key_missing.yaml +++ b/tests/test_yaml_run_args/run_args_key_missing.yaml @@ -20,6 +20,4 @@ searcher_kwargs: initial_design_size: 5 surrogate_model: gp pre_load_hooks: - hook1: - path: "tests/test_yaml_run_args/test_yaml_run_args.py" - name: hook1 + hook1: "tests/test_yaml_run_args/test_yaml_run_args.py" diff --git a/tests/test_yaml_run_args/run_args_optional_loading_format.yaml b/tests/test_yaml_run_args/run_args_optional_loading_format.yaml index 510c655c..9c447806 100644 --- a/tests/test_yaml_run_args/run_args_optional_loading_format.yaml +++ b/tests/test_yaml_run_args/run_args_optional_loading_format.yaml @@ -24,6 +24,4 @@ searcher_kwargs: initial_design_size: 5 surrogate_model: gp pre_load_hooks: - hook1: - path: "tests/test_yaml_run_args/test_yaml_run_args.py" - name: hook1 + hook1: "tests/test_yaml_run_args/test_yaml_run_args.py" diff --git a/tests/test_yaml_run_args/run_args_wrong_name.yaml b/tests/test_yaml_run_args/run_args_wrong_name.yaml index 0b187332..c0b451c4 100644 --- a/tests/test_yaml_run_args/run_args_wrong_name.yaml +++ b/tests/test_yaml_run_args/run_args_wrong_name.yaml @@ -31,9 +31,5 @@ search: surrogate_model: gp pre_load_hooks: - hook1: - path: "tests/test_yaml_run_args/test_yaml_run_args.py" - name: hook1 - hook2: - path: "tests/test_yaml_run_args/test_yaml_run_args.py" - name: hook2 + hook1: "tests/test_yaml_run_args/test_yaml_run_args.py" + hook2: "tests/test_yaml_run_args/test_yaml_run_args.py" diff --git a/tests/test_yaml_run_args/run_args_wrong_path.yaml b/tests/test_yaml_run_args/run_args_wrong_path.yaml index 47abfe04..95254e69 100644 --- a/tests/test_yaml_run_args/run_args_wrong_path.yaml +++ b/tests/test_yaml_run_args/run_args_wrong_path.yaml @@ -31,9 +31,5 @@ search: surrogate_model: gp pre_load_hooks: - hook1: - path: "tests/test_yaml_run_args/test_yaml_run_args.py" - name: hook1 - hook2: - path: "tests/test_yaml_run_args/test_yaml_run_args.py" - name: hook2 + hook1: "tests/test_yaml_run_args/test_yaml_run_args.py" + hook2: "tests/test_yaml_run_args/test_yaml_run_args.py" diff --git a/tests/test_yaml_run_args/test_declarative_usage_docs/defining_hooks.yaml b/tests/test_yaml_run_args/test_declarative_usage_docs/defining_hooks.yaml index 186acaab..922bb542 100644 --- a/tests/test_yaml_run_args/test_declarative_usage_docs/defining_hooks.yaml +++ b/tests/test_yaml_run_args/test_declarative_usage_docs/defining_hooks.yaml @@ -1,7 +1,7 @@ # Basic NEPS Configuration Example run_pipeline: - path: path/to/your/run_pipeline.py # Path to the function file - name: example_pipeline # Function name within the file + path: tests/test_yaml_run_args/test_declarative_usage_docs/run_pipeline.py + name: run_pipeline_constant pipeline_space: learning_rate: @@ -16,9 +16,10 @@ pipeline_space: choices: [adam, sgd, adamw] batch_size: 64 -root_directory: path/to/results # Directory for result storage +root_directory: path/to/results/hooks # Directory for result storage max_evaluations_total: 20 # Budget pre_load_hooks: - hook1: path/to/your/hooks.py # (function_name: Path to the function's file) - hook2: path/to/your/hooks.py # Different function name from the same file source + hook1: tests/test_yaml_run_args/test_declarative_usage_docs/hooks.py + +overwrite_working_directory: True diff --git a/tests/test_yaml_run_args/test_declarative_usage_docs/hooks.py b/tests/test_yaml_run_args/test_declarative_usage_docs/hooks.py new file mode 100644 index 00000000..a26f28f3 --- /dev/null +++ b/tests/test_yaml_run_args/test_declarative_usage_docs/hooks.py @@ -0,0 +1,8 @@ +def hook1(sampler): + """func to test loading of pre_load_hooks""" + return sampler + + +def hook2(sampler): + """func to test loading of pre_load_hooks""" + return sampler diff --git a/tests/test_yaml_run_args/test_declarative_usage_docs/simple_example.yaml b/tests/test_yaml_run_args/test_declarative_usage_docs/simple_example.yaml index d50419fe..a167964c 100644 --- a/tests/test_yaml_run_args/test_declarative_usage_docs/simple_example.yaml +++ b/tests/test_yaml_run_args/test_declarative_usage_docs/simple_example.yaml @@ -14,3 +14,6 @@ pipeline_space: root_directory: path/to/results/simple_example # Directory for result storage max_evaluations_total: 20 # Budget + + +overwrite_working_directory: True diff --git a/tests/test_yaml_run_args/test_declarative_usage_docs/simple_example_including_run_pipeline.yaml b/tests/test_yaml_run_args/test_declarative_usage_docs/simple_example_including_run_pipeline.yaml index 6968acd7..53cde312 100644 --- a/tests/test_yaml_run_args/test_declarative_usage_docs/simple_example_including_run_pipeline.yaml +++ b/tests/test_yaml_run_args/test_declarative_usage_docs/simple_example_including_run_pipeline.yaml @@ -18,3 +18,5 @@ pipeline_space: root_directory: path/to/results/simple_example_including_run_pipeline # Directory for result storage max_evaluations_total: 20 # Budget + +overwrite_working_directory: True diff --git a/tests/test_yaml_run_args/test_declarative_usage_docs/test_declarative_usage_docs.py b/tests/test_yaml_run_args/test_declarative_usage_docs/test_declarative_usage_docs.py index d5f39082..ad6d94f7 100644 --- a/tests/test_yaml_run_args/test_declarative_usage_docs/test_declarative_usage_docs.py +++ b/tests/test_yaml_run_args/test_declarative_usage_docs/test_declarative_usage_docs.py @@ -8,7 +8,8 @@ @pytest.mark.neps_api @pytest.mark.parametrize("yaml_file", [ "simple_example_including_run_pipeline.yaml", - "full_configuration_template.yaml" + "full_configuration_template.yaml", + "defining_hooks.yaml" ]) def test_run_with_yaml(yaml_file: str) -> None: """ From 13231b24625280f8375d2776ebc1a796998fcab1 Mon Sep 17 00:00:00 2001 From: Daniel <63580393+danrgll@users.noreply.github.com> Date: Wed, 5 Jun 2024 15:27:21 +0200 Subject: [PATCH 05/29] simplify code --- neps/search_spaces/yaml_search_space_utils.py | 24 ++++++------------- 1 file changed, 7 insertions(+), 17 deletions(-) diff --git a/neps/search_spaces/yaml_search_space_utils.py b/neps/search_spaces/yaml_search_space_utils.py index 8606a3e5..27c268e4 100644 --- a/neps/search_spaces/yaml_search_space_utils.py +++ b/neps/search_spaces/yaml_search_space_utils.py @@ -99,26 +99,16 @@ def deduce_type( TypeError: If the type cannot be deduced or the details don't align with expected constraints. """ - try: - # Deduce type + if isinstance(details, (str, int, float)): + param_type = "const" + elif isinstance(details, dict): if "type" in details: param_type = details.pop("type").lower() else: - # because details could be string - if isinstance(details, (str, int, float)): - param_type = "const" - return param_type - else: - param_type = deduce_param_type(name, details) - except TypeError as e: - # because details could be int, float - if isinstance(details, (str, int, float)): - param_type = "const" - return param_type - else: - raise TypeError( - f"Unable to deduce parameter type for '{name}' with details '{details}'.") \ - from e + param_type = deduce_param_type(name, details) + else: + raise TypeError( + f"Unable to deduce parameter type for '{name}' with details '{details}'.") return param_type From 20cbb318923ed2dcb056cb8d969357ee49997d65 Mon Sep 17 00:00:00 2001 From: Daniel <63580393+danrgll@users.noreply.github.com> Date: Wed, 5 Jun 2024 16:04:12 +0200 Subject: [PATCH 06/29] add docstring rm solved ToDos --- neps/utils/run_args_from_yaml.py | 22 +++++++++++++++++++--- 1 file changed, 19 insertions(+), 3 deletions(-) diff --git a/neps/utils/run_args_from_yaml.py b/neps/utils/run_args_from_yaml.py index e95b331e..8c33a38c 100644 --- a/neps/utils/run_args_from_yaml.py +++ b/neps/utils/run_args_from_yaml.py @@ -257,11 +257,28 @@ def process_config_key(settings: Dict, special_configs: Dict, keys: List) -> Non def process_pipeline_space(key, special_configs, settings): + """ + Process or load the pipeline space configuration. + + This function checks if the given key exists in the `special_configs` dictionary. + If it exists, it processes the associated value, which can be either a dictionary + or a string. Based on the keys of the dictionary it decides if the pipeline_space + have to be loaded or needs to be converted into a neps search_space structure. + The processed pipeline space is then stored in the `settings` + dictionary under the given key. + + Args: + key (str): The key to check in the `special_configs` dictionary. + special_configs (dict): The dictionary containing special configuration values. + settings (dict): The dictionary where the processed pipeline space will be stored. + + Raises: + TypeError: If the value associated with the key is neither a string nor a + dictionary. + """ if special_configs.get(key) is not None: pipeline_space = special_configs[key] if isinstance(pipeline_space, dict): - # TODO: was ist wenn ein argument fehlt - # determine if dict contains path_loading or the actual search space expected_keys = {"path", "name"} actual_keys = set(pipeline_space.keys()) @@ -454,7 +471,6 @@ def check_essential_arguments( if not root_directory: raise ValueError("'root_directory' is required but was not provided.") if not pipeline_space: - # ToDO: irg was falsch # handling special case for searcher instance, in which user doesn't have to # provide the search_space because it's the argument of the searcher. if run_args or not isinstance(searcher, BaseOptimizer): From 044c61a5dcae9251852a35f4784555f3b35484b9 Mon Sep 17 00:00:00 2001 From: Daniel <63580393+danrgll@users.noreply.github.com> Date: Wed, 5 Jun 2024 23:10:14 +0200 Subject: [PATCH 07/29] change design for searcher config --- neps/api.py | 9 ++-- neps/optimizers/default_searchers/asha.yaml | 26 +++++----- .../default_searchers/asha_prior.yaml | 26 +++++----- .../bayesian_optimization.yaml | 34 ++++++------ .../default_searchers/hyperband.yaml | 24 ++++----- .../optimizers/default_searchers/mobster.yaml | 46 ++++++++-------- neps/optimizers/default_searchers/pibo.yaml | 36 ++++++------- .../default_searchers/priorband.yaml | 38 +++++++------- .../default_searchers/priorband_bo.yaml | 52 +++++++++---------- .../default_searchers/random_search.yaml | 10 ++-- .../regularized_evolution.yaml | 18 +++---- .../default_searchers/successive_halving.yaml | 26 +++++----- .../successive_halving_prior.yaml | 26 +++++----- neps/utils/run_args_from_yaml.py | 6 ++- .../testing_yaml/optimizer_test.yaml | 23 ++++---- 15 files changed, 190 insertions(+), 210 deletions(-) diff --git a/neps/api.py b/neps/api.py index bb76f50c..9a5d47e9 100644 --- a/neps/api.py +++ b/neps/api.py @@ -438,10 +438,11 @@ def _run_args( # Fetching the searcher data, throws an error when the searcher is not found config = get_searcher_data(searcher) - searcher_alg = config["searcher_init"]["algorithm"] - searcher_config = ( - {} if config["searcher_kwargs"] is None else config["searcher_kwargs"] - ) + if "algorithm" in config: + searcher_alg = config.pop("algorithm") + else: + raise KeyError(f"Missing key algorithm in searcher config:{config}") + searcher_config = config logger.info(f"Running {searcher} as the searcher") logger.info(f"Algorithm: {searcher_alg}") diff --git a/neps/optimizers/default_searchers/asha.yaml b/neps/optimizers/default_searchers/asha.yaml index 169adf36..8d914ad4 100644 --- a/neps/optimizers/default_searchers/asha.yaml +++ b/neps/optimizers/default_searchers/asha.yaml @@ -1,15 +1,13 @@ -searcher_init: - algorithm: asha -searcher_kwargs: - # Arguments that can be modified by the user - eta: 3 - early_stopping_rate: 0 - initial_design_type: max_budget - use_priors: false - random_interleave_prob: 0.0 - sample_default_first: false - sample_default_at_target: false +algorithm: asha +# Arguments that can be modified by the user +eta: 3 +early_stopping_rate: 0 +initial_design_type: max_budget +use_priors: false +random_interleave_prob: 0.0 +sample_default_first: false +sample_default_at_target: false - # Arguments that can not be modified by the user - # sampling_policy: RandomUniformPolicy - # promotion_policy: AsyncPromotionPolicy +# Arguments that can not be modified by the user +# sampling_policy: RandomUniformPolicy +# promotion_policy: AsyncPromotionPolicy diff --git a/neps/optimizers/default_searchers/asha_prior.yaml b/neps/optimizers/default_searchers/asha_prior.yaml index e43ecdd2..dc6852d1 100644 --- a/neps/optimizers/default_searchers/asha_prior.yaml +++ b/neps/optimizers/default_searchers/asha_prior.yaml @@ -1,15 +1,13 @@ -searcher_init: - algorithm: asha_prior -searcher_kwargs: - # Arguments that can be modified by the user - eta: 3 - early_stopping_rate: 0 - initial_design_type: max_budget - prior_confidence: medium # or {"low", "high"} - random_interleave_prob: 0.0 - sample_default_first: false - sample_default_at_target: false +algorithm: asha_prior +# Arguments that can be modified by the user +eta: 3 +early_stopping_rate: 0 +initial_design_type: max_budget +prior_confidence: medium # or {"low", "high"} +random_interleave_prob: 0.0 +sample_default_first: false +sample_default_at_target: false - # Arguments that can not be modified by the user - # sampling_policy: FixedPriorPolicy - # promotion_policy: AsyncPromotionPolicy +# Arguments that can not be modified by the user +# sampling_policy: FixedPriorPolicy +# promotion_policy: AsyncPromotionPolicy diff --git a/neps/optimizers/default_searchers/bayesian_optimization.yaml b/neps/optimizers/default_searchers/bayesian_optimization.yaml index ffc18c82..26d2c507 100644 --- a/neps/optimizers/default_searchers/bayesian_optimization.yaml +++ b/neps/optimizers/default_searchers/bayesian_optimization.yaml @@ -1,19 +1,17 @@ -searcher_init: - algorithm: bayesian_optimization -searcher_kwargs: - # Arguments that can be modified by the user - initial_design_size: 10 - surrogate_model: gp # or {"gp_hierarchy"} - acquisition: EI # or {"LogEI", "AEI"} - log_prior_weighted: false - acquisition_sampler: mutation # or {"random", "evolution"} - random_interleave_prob: 0.0 - disable_priors: true - sample_default_first: false +algorithm: bayesian_optimization +# Arguments that can be modified by the user +initial_design_size: 10 +surrogate_model: gp # or {"gp_hierarchy"} +acquisition: EI # or {"LogEI", "AEI"} +log_prior_weighted: false +acquisition_sampler: mutation # or {"random", "evolution"} +random_interleave_prob: 0.0 +disable_priors: true +sample_default_first: false - # Other arguments: - # surrogate_model_args: None # type: dict - # optimal_assignment: false # type: bool - # domain_se_kernel: None # type: str - # graph_kernels: None # type: list - # hp_kernels: None # type: list +# Other arguments: +# surrogate_model_args: None # type: dict +# optimal_assignment: false # type: bool +# domain_se_kernel: None # type: str +# graph_kernels: None # type: list +# hp_kernels: None # type: list diff --git a/neps/optimizers/default_searchers/hyperband.yaml b/neps/optimizers/default_searchers/hyperband.yaml index d3a16145..d39fb139 100644 --- a/neps/optimizers/default_searchers/hyperband.yaml +++ b/neps/optimizers/default_searchers/hyperband.yaml @@ -1,14 +1,12 @@ -searcher_init: - algorithm: hyperband -searcher_kwargs: - # Arguments that can be modified by the user - eta: 3 - initial_design_type: max_budget - use_priors: false - random_interleave_prob: 0.0 - sample_default_first: false - sample_default_at_target: false +algorithm: hyperband +# Arguments that can be modified by the user +eta: 3 +initial_design_type: max_budget +use_priors: false +random_interleave_prob: 0.0 +sample_default_first: false +sample_default_at_target: false - # Arguments that can not be modified by the user - # sampling_policy: RandomUniformPolicy - # promotion_policy: SyncPromotionPolicy +# Arguments that can not be modified by the user +# sampling_policy: RandomUniformPolicy +# promotion_policy: SyncPromotionPolicy diff --git a/neps/optimizers/default_searchers/mobster.yaml b/neps/optimizers/default_searchers/mobster.yaml index c016000b..385b74ad 100644 --- a/neps/optimizers/default_searchers/mobster.yaml +++ b/neps/optimizers/default_searchers/mobster.yaml @@ -1,27 +1,25 @@ -searcher_init: - algorithm: mobster -searcher_kwargs: - # Arguments that can be modified by the user - eta: 3 - initial_design_type: max_budget - use_priors: false - random_interleave_prob: 0.0 - sample_default_first: false - sample_default_at_target: false +algorithm: mobster +# Arguments that can be modified by the user +eta: 3 +initial_design_type: max_budget +use_priors: false +random_interleave_prob: 0.0 +sample_default_first: false +sample_default_at_target: false - # arguments for model - surrogate_model: gp # or {"gp_hierarchy"} - acquisition: EI # or {"LogEI", "AEI"} - log_prior_weighted: false - acquisition_sampler: random # or {"mutation", "evolution"} +# arguments for model +surrogate_model: gp # or {"gp_hierarchy"} +acquisition: EI # or {"LogEI", "AEI"} +log_prior_weighted: false +acquisition_sampler: random # or {"mutation", "evolution"} - # Arguments that can not be modified by the user - # sampling_policy: RandomUniformPolicy - # promotion_policy: AsyncPromotionPolicy - # model_policy: ModelPolicy +# Arguments that can not be modified by the user +# sampling_policy: RandomUniformPolicy +# promotion_policy: AsyncPromotionPolicy +# model_policy: ModelPolicy - # Other arguments - # surrogate_model_args: None # type: dict - # domain_se_kernel: None # type: str - # graph_kernels: None # type: list - # hp_kernels: None # type: list +# Other arguments +# surrogate_model_args: None # type: dict +# domain_se_kernel: None # type: str +# graph_kernels: None # type: list +# hp_kernels: None # type: list diff --git a/neps/optimizers/default_searchers/pibo.yaml b/neps/optimizers/default_searchers/pibo.yaml index 8c4fecf6..b75a23f3 100644 --- a/neps/optimizers/default_searchers/pibo.yaml +++ b/neps/optimizers/default_searchers/pibo.yaml @@ -1,20 +1,18 @@ -searcher_init: - algorithm: bayesian_optimization -searcher_kwargs: - # Arguments that can be modified by the user - initial_design_size: 10 - surrogate_model: gp # or {"gp_hierarchy"} - acquisition: EI # or {"LogEI", "AEI"} - log_prior_weighted: false - acquisition_sampler: mutation # or {"random", "evolution"} - random_interleave_prob: 0.0 - disable_priors: false - prior_confidence: medium # or {"low", "high"} - sample_default_first: false +algorithm: bayesian_optimization +# Arguments that can be modified by the user +initial_design_size: 10 +surrogate_model: gp # or {"gp_hierarchy"} +acquisition: EI # or {"LogEI", "AEI"} +log_prior_weighted: false +acquisition_sampler: mutation # or {"random", "evolution"} +random_interleave_prob: 0.0 +disable_priors: false +prior_confidence: medium # or {"low", "high"} +sample_default_first: false - # Other arguments: - # surrogate_model_args: None # type: dict - # optimal_assignment: false # type: bool - # domain_se_kernel: None # type: str - # graph_kernels: None # type: list - # hp_kernels: None # type: list +# Other arguments: +# surrogate_model_args: None # type: dict +# optimal_assignment: false # type: bool +# domain_se_kernel: None # type: str +# graph_kernels: None # type: list +# hp_kernels: None # type: list diff --git a/neps/optimizers/default_searchers/priorband.yaml b/neps/optimizers/default_searchers/priorband.yaml index 9b11ae9a..f5a16f6f 100644 --- a/neps/optimizers/default_searchers/priorband.yaml +++ b/neps/optimizers/default_searchers/priorband.yaml @@ -1,22 +1,20 @@ -searcher_init: - algorithm: priorband -searcher_kwargs: - # Arguments that can be modified by the user - eta: 3 - initial_design_type: max_budget - prior_confidence: medium # or {"low", "high"} - random_interleave_prob: 0.0 - sample_default_first: true - sample_default_at_target: false - prior_weight_type: geometric - inc_sample_type: mutation - inc_mutation_rate: 0.5 - inc_mutation_std: 0.25 - inc_style: dynamic +algorithm: priorband +# Arguments that can be modified by the user +eta: 3 +initial_design_type: max_budget +prior_confidence: medium # or {"low", "high"} +random_interleave_prob: 0.0 +sample_default_first: true +sample_default_at_target: false +prior_weight_type: geometric +inc_sample_type: mutation +inc_mutation_rate: 0.5 +inc_mutation_std: 0.25 +inc_style: dynamic - # arguments for model - model_based: false # crucial argument to set to allow model-search +# arguments for model +model_based: false # crucial argument to set to allow model-search - # Arguments that can not be modified by the user - # sampling_policy: EnsemblePolicy - # promotion_policy: SyncPromotionPolicy +# Arguments that can not be modified by the user +# sampling_policy: EnsemblePolicy +# promotion_policy: SyncPromotionPolicy diff --git a/neps/optimizers/default_searchers/priorband_bo.yaml b/neps/optimizers/default_searchers/priorband_bo.yaml index 04e530c1..134ff78c 100644 --- a/neps/optimizers/default_searchers/priorband_bo.yaml +++ b/neps/optimizers/default_searchers/priorband_bo.yaml @@ -1,32 +1,30 @@ -searcher_init: - algorithm: priorband -searcher_kwargs: - # Arguments that can be modified by the user - eta: 3 - initial_design_type: max_budget - prior_confidence: medium # or {"low", "high"} - random_interleave_prob: 0.0 - sample_default_first: true - sample_default_at_target: false - prior_weight_type: geometric - inc_sample_type: mutation - inc_mutation_rate: 0.5 - inc_mutation_std: 0.25 - inc_style: dynamic +algorithm: priorband +# Arguments that can be modified by the user +eta: 3 +initial_design_type: max_budget +prior_confidence: medium # or {"low", "high"} +random_interleave_prob: 0.0 +sample_default_first: true +sample_default_at_target: false +prior_weight_type: geometric +inc_sample_type: mutation +inc_mutation_rate: 0.5 +inc_mutation_std: 0.25 +inc_style: dynamic - # arguments for model - model_based: true # crucial argument to set to allow model-search - modelling_type: joint - initial_design_size: 10 - surrogate_model: gp # or {"gp_hierarchy"} - acquisition: EI # or {"LogEI", "AEI"} - log_prior_weighted: false - acquisition_sampler: mutation # or {"random", "evolution"} +# arguments for model +model_based: true # crucial argument to set to allow model-search +modelling_type: joint +initial_design_size: 10 +surrogate_model: gp # or {"gp_hierarchy"} +acquisition: EI # or {"LogEI", "AEI"} +log_prior_weighted: false +acquisition_sampler: mutation # or {"random", "evolution"} - # Arguments that can not be modified by the user - # sampling_policy: EnsemblePolicy - # promotion_policy: SyncPromotionPolicy - # model_policy: ModelPolicy +# Arguments that can not be modified by the user +# sampling_policy: EnsemblePolicy +# promotion_policy: SyncPromotionPolicy +# model_policy: ModelPolicy # Other arguments # surrogate_model_args: None # type: dict diff --git a/neps/optimizers/default_searchers/random_search.yaml b/neps/optimizers/default_searchers/random_search.yaml index 745aeef5..848a7873 100644 --- a/neps/optimizers/default_searchers/random_search.yaml +++ b/neps/optimizers/default_searchers/random_search.yaml @@ -1,6 +1,4 @@ -searcher_init: - algorithm: random_search -searcher_kwargs: - # Arguments that can be modified by the user - use_priors: false - ignore_fidelity: true +algorithm: random_search +# Arguments that can be modified by the user +use_priors: false +ignore_fidelity: true diff --git a/neps/optimizers/default_searchers/regularized_evolution.yaml b/neps/optimizers/default_searchers/regularized_evolution.yaml index efb62a6a..0e298423 100644 --- a/neps/optimizers/default_searchers/regularized_evolution.yaml +++ b/neps/optimizers/default_searchers/regularized_evolution.yaml @@ -1,11 +1,9 @@ -searcher_init: - algorithm: regularized_evolution -searcher_kwargs: - # Arguments that can be modified by the user - population_size: 30 - sample_size: 10 - assisted: false +algorithm: regularized_evolution +# Arguments that can be modified by the user +population_size: 30 +sample_size: 10 +assisted: false - # Other arguments - # assisted_zero_cost_proxy: None # type: Callable - # assisted_init_population_dir: None # type: str | Path +# Other arguments +# assisted_zero_cost_proxy: None # type: Callable +# assisted_init_population_dir: None # type: str | Path diff --git a/neps/optimizers/default_searchers/successive_halving.yaml b/neps/optimizers/default_searchers/successive_halving.yaml index 17161685..148cbc69 100644 --- a/neps/optimizers/default_searchers/successive_halving.yaml +++ b/neps/optimizers/default_searchers/successive_halving.yaml @@ -1,15 +1,13 @@ -searcher_init: - algorithm: successive_halving -searcher_kwargs: - # Arguments that can be modified by the user - eta: 3 - early_stopping_rate: 0 - initial_design_type: max_budget - use_priors: false - random_interleave_prob: 0.0 - sample_default_first: false - sample_default_at_target: false +algorithm: successive_halving +# Arguments that can be modified by the user +eta: 3 +early_stopping_rate: 0 +initial_design_type: max_budget +use_priors: false +random_interleave_prob: 0.0 +sample_default_first: false +sample_default_at_target: false - # Arguments that can not be modified by the user - # sampling_policy: RandomUniformPolicy - # promotion_policy: SyncPromotionPolicy +# Arguments that can not be modified by the user +# sampling_policy: RandomUniformPolicy +# promotion_policy: SyncPromotionPolicy diff --git a/neps/optimizers/default_searchers/successive_halving_prior.yaml b/neps/optimizers/default_searchers/successive_halving_prior.yaml index f107940d..29b320dd 100644 --- a/neps/optimizers/default_searchers/successive_halving_prior.yaml +++ b/neps/optimizers/default_searchers/successive_halving_prior.yaml @@ -1,15 +1,13 @@ -searcher_init: - algorithm: successive_halving_prior -searcher_kwargs: - # Arguments that can be modified by the user - eta: 3 - early_stopping_rate: 0 - initial_design_type: max_budget - prior_confidence: medium # or {"low", "high"} - random_interleave_prob: 0.0 - sample_default_first: false - sample_default_at_target: false +algorithm: successive_halving_prior +# Arguments that can be modified by the user +eta: 3 +early_stopping_rate: 0 +initial_design_type: max_budget +prior_confidence: medium # or {"low", "high"} +random_interleave_prob: 0.0 +sample_default_first: false +sample_default_at_target: false - # Arguments that can not be modified by the user - # sampling_policy: FixedPriorPolicy - # promotion_policy: SyncPromotionPolicy +# Arguments that can not be modified by the user +# sampling_policy: FixedPriorPolicy +# promotion_policy: SyncPromotionPolicy diff --git a/neps/utils/run_args_from_yaml.py b/neps/utils/run_args_from_yaml.py index 8c33a38c..c67aded3 100644 --- a/neps/utils/run_args_from_yaml.py +++ b/neps/utils/run_args_from_yaml.py @@ -85,7 +85,11 @@ def get_run_args_from_yaml(path: str) -> dict: if parameter in expected_parameters: settings[parameter] = value else: - raise KeyError(f"Parameter '{parameter}' is not an argument of neps.run().") + raise KeyError(f"Parameter '{parameter}' is not an argument of neps.run() " + f"provided via run_args." + f"See here all valid arguments:" + f" {', '.join(expected_parameters)}, " + f"'run_pipeline', 'preload_hooks', 'pipeline_space'") # Process complex configurations (e.g., 'pipeline_space', 'searcher') and integrate # them into 'settings'. diff --git a/tests/test_neps_api/testing_yaml/optimizer_test.yaml b/tests/test_neps_api/testing_yaml/optimizer_test.yaml index 96c585a7..9d7e27f7 100644 --- a/tests/test_neps_api/testing_yaml/optimizer_test.yaml +++ b/tests/test_neps_api/testing_yaml/optimizer_test.yaml @@ -1,12 +1,11 @@ -searcher_init: - algorithm: bayesian_optimization -searcher_kwargs: # Specific arguments depending on the searcher - initial_design_size: 7 - surrogate_model: gp - acquisition: EI - log_prior_weighted: false - acquisition_sampler: random - random_interleave_prob: 0.1 - disable_priors: false - prior_confidence: high - sample_default_first: false +algorithm: bayesian_optimization +# Specific arguments depending on the searcher +initial_design_size: 7 +surrogate_model: gp +acquisition: EI +log_prior_weighted: false +acquisition_sampler: random +random_interleave_prob: 0.1 +disable_priors: false +prior_confidence: high +sample_default_first: false From a2f259b6b538503c04ab586d3c28b2c70193c3e9 Mon Sep 17 00:00:00 2001 From: Daniel <63580393+danrgll@users.noreply.github.com> Date: Wed, 5 Jun 2024 23:40:00 +0200 Subject: [PATCH 08/29] fix pipeline_space example --- docs/reference/optimizers.md | 23 +++++++------- .../basic_usage/search_space_example.yaml | 0 neps_examples/yaml_usage/pipeline_space.yaml | 31 +++++++++---------- 3 files changed, 26 insertions(+), 28 deletions(-) delete mode 100644 neps_examples/basic_usage/search_space_example.yaml diff --git a/docs/reference/optimizers.md b/docs/reference/optimizers.md index 5838d974..927ebe14 100644 --- a/docs/reference/optimizers.md +++ b/docs/reference/optimizers.md @@ -59,18 +59,17 @@ The library will then load your custom settings and use them for optimization. Here's the format of a custom YAML (`custom_bo.yaml`) configuration using `Bayesian Optimization` as an example: ```yaml -searcher_init: - algorithm: bayesian_optimization -searcher_kwargs: # Specific arguments depending on the searcher - initial_design_size: 7 - surrogate_model: gp - acquisition: EI - log_prior_weighted: false - acquisition_sampler: random - random_interleave_prob: 0.1 - disable_priors: false - prior_confidence: high - sample_default_first: false +algorithm: bayesian_optimization +# Specific arguments depending on the searcher +initial_design_size: 7 +surrogate_model: gp +acquisition: EI +log_prior_weighted: false +acquisition_sampler: random +random_interleave_prob: 0.1 +disable_priors: false +prior_confidence: high +sample_default_first: false ``` ```python diff --git a/neps_examples/basic_usage/search_space_example.yaml b/neps_examples/basic_usage/search_space_example.yaml deleted file mode 100644 index e69de29b..00000000 diff --git a/neps_examples/yaml_usage/pipeline_space.yaml b/neps_examples/yaml_usage/pipeline_space.yaml index c052c7e8..bbc8715f 100644 --- a/neps_examples/yaml_usage/pipeline_space.yaml +++ b/neps_examples/yaml_usage/pipeline_space.yaml @@ -1,16 +1,15 @@ -pipeline_space: - epochs: - lower: 1 - upper: 5 - learning_rate: - lower: 1e-5 - upper: 1e-1 - log: True - num_layers: - lower: 1 - upper: 5 - optimizer: - choices: ["adam", "sgd"] - num_neurons: - lower: 64 - upper: 128 +epochs: + lower: 1 + upper: 5 +learning_rate: + lower: 1e-5 + upper: 1e-1 + log: True +num_layers: + lower: 1 + upper: 5 +optimizer: + choices: ["adam", "sgd"] +num_neurons: + lower: 64 + upper: 128 From f8f36ae1721aa02260f5d6688c010902a3d24ee6 Mon Sep 17 00:00:00 2001 From: Daniel <63580393+danrgll@users.noreply.github.com> Date: Fri, 7 Jun 2024 13:58:39 +0200 Subject: [PATCH 09/29] rm neps argument searcher_path --- neps/api.py | 28 +++++++++++-------- neps/utils/common.py | 19 +++++++------ neps/utils/run_args_from_yaml.py | 3 -- .../testing_scripts/user_yaml_neps.py | 5 ++-- tests/test_yaml_run_args/run_args_empty.yaml | 1 - tests/test_yaml_run_args/run_args_full.yaml | 1 - .../run_args_full_same_level.yaml | 1 - .../run_args_invalid_key.yaml | 1 - .../run_args_invalid_type.yaml | 1 - .../run_args_key_missing.yaml | 1 - .../run_args_optional_loading_format.yaml | 1 - .../test_yaml_run_args/run_args_partial.yaml | 1 - .../run_args_partial_same_level.yaml | 1 - .../run_args_wrong_name.yaml | 1 - .../run_args_wrong_path.yaml | 1 - .../test_yaml_run_args/test_yaml_run_args.py | 3 -- 16 files changed, 29 insertions(+), 40 deletions(-) diff --git a/neps/api.py b/neps/api.py index 9a5d47e9..83a2f16c 100644 --- a/neps/api.py +++ b/neps/api.py @@ -26,6 +26,17 @@ from neps.utils.common import get_searcher_data, get_value from neps.utils.data_loading import _get_loss +VALID_SEARCHER = [ + "default", + "bayesian_optimization", + "random_search", + "hyperband", + "priorband", + "mobster", + "asha", + "regularized_evolution", +] + def _post_evaluation_hook_function( _loss_value_on_error: None | float, _ignore_errors: bool @@ -132,9 +143,8 @@ def run( "asha", "regularized_evolution", ] - | BaseOptimizer + | BaseOptimizer | Path ) = "default", - searcher_path: Path | str | None = None, **searcher_kwargs, ) -> None: """Run a neural pipeline search. @@ -176,9 +186,8 @@ def run( cost_value_on_error: Setting this and loss_value_on_error to any float will supress any error and will use given cost value instead. default: None pre_load_hooks: List of functions that will be called before load_results(). - searcher: Which optimizer to use. This is usually only needed by neps developers. - searcher_path: The path to the user created searcher. None when the user - is using NePS designed searchers. + searcher: Which optimizer to use. Can be a string identifier, an + instance of BaseOptimizer, or a Path to a custom optimizer. **searcher_kwargs: Will be passed to the searcher. This is usually only needed by neps develolpers. @@ -250,7 +259,6 @@ def run( cost_value_on_error) pre_load_hooks = optim_settings.get("pre_load_hooks", pre_load_hooks) searcher = optim_settings.get("searcher", searcher) - searcher_path = optim_settings.get("searcher_path", searcher_path) for key, value in optim_settings.get("searcher_kwargs", searcher_kwargs).items(): searcher_kwargs[key] = value @@ -310,7 +318,6 @@ def run( cost_value_on_error=cost_value_on_error, logger=logger, searcher=searcher, - searcher_path=searcher_path, **searcher_kwargs, ) @@ -380,7 +387,6 @@ def _run_args( ] | BaseOptimizer ) = "default", - searcher_path: Path | str | None = None, **searcher_kwargs, ) -> tuple[BaseOptimizer, dict]: try: @@ -412,11 +418,11 @@ def _run_args( message = f"The pipeline_space has invalid type: {type(pipeline_space)}" raise TypeError(message) from e - if isinstance(searcher, str) and searcher_path is not None: + if isinstance(searcher, (str, Path)) and searcher not in VALID_SEARCHER: # The users has their own custom searcher. logging.info("Preparing to run user created searcher") - config = get_searcher_data(searcher, searcher_path) + config, searcher = get_searcher_data(searcher, loading_custom_searcher=True) searcher_info["searcher_selection"] = "user-yaml" searcher_info["neps_decision_tree"] = False else: @@ -436,7 +442,7 @@ def _run_args( searcher_info["neps_decision_tree"] = False searcher_info["searcher_selection"] = "neps-default" # Fetching the searcher data, throws an error when the searcher is not found - config = get_searcher_data(searcher) + config, searcher = get_searcher_data(searcher) if "algorithm" in config: searcher_alg = config.pop("algorithm") diff --git a/neps/utils/common.py b/neps/utils/common.py index 677c0b86..c362c855 100644 --- a/neps/utils/common.py +++ b/neps/utils/common.py @@ -193,21 +193,19 @@ def get_initial_directory(pipeline_directory: Path | str | None = None) -> Path: def get_searcher_data( - searcher: str, - searcher_path: Path | str | None = None, -) -> dict[str, Any]: + searcher: str | Path, loading_custom_searcher: bool = False +) -> (dict[str, Any], str): """Returns the data from the YAML file associated with the specified searcher. Args: searcher: The name of the searcher. - searcher_path: The path to the directory where the searcher defined YAML file - is located. + loading_custom_searcher: Flag if searcher contains a custom yaml Returns: - The content of the YAML file. + The content of the YAML file and searcher name. """ - if searcher_path is not None: - user_yaml_path = Path(searcher_path, searcher).with_suffix(".yaml") + if loading_custom_searcher: + user_yaml_path = Path(searcher).with_suffix(".yaml") if not user_yaml_path.exists(): raise FileNotFoundError( @@ -218,6 +216,9 @@ def get_searcher_data( with user_yaml_path.open("r") as file: data = yaml.safe_load(file) + file_name = user_yaml_path.stem + searcher = data.get("name", file_name) + else: # TODO(eddiebergman): This is a bad idea as it relies on folder structure to be # correct, we should either have a dedicated resource folder or at least have @@ -246,7 +247,7 @@ def get_searcher_data( with resource_path.open() as file: data = yaml.safe_load(file) - return data # type: ignore + return data, searcher # type: ignore # TODO(eddiebergman): This seems like a bad function name, I guess this is used for a diff --git a/neps/utils/run_args_from_yaml.py b/neps/utils/run_args_from_yaml.py index c67aded3..3ef4a383 100644 --- a/neps/utils/run_args_from_yaml.py +++ b/neps/utils/run_args_from_yaml.py @@ -25,7 +25,6 @@ COST_VALUE_ON_ERROR = "cost_value_on_error" IGNORE_ERROR = "ignore_errors" SEARCHER = "searcher" -SEARCHER_PATH = "searcher_path" PRE_LOAD_HOOKS = "pre_load_hooks" SEARCHER_KWARGS = "searcher_kwargs" MAX_EVALUATIONS_PER_RUN = "max_evaluations_per_run" @@ -73,7 +72,6 @@ def get_run_args_from_yaml(path: str) -> dict: LOSS_VALUE_ON_ERROR, COST_VALUE_ON_ERROR, IGNORE_ERROR, - SEARCHER_PATH, ] # Flatten the YAML file's structure to separate flat parameters (flat_config) and @@ -412,7 +410,6 @@ def check_run_args(settings: Dict) -> None: LOSS_VALUE_ON_ERROR: float, COST_VALUE_ON_ERROR: float, IGNORE_ERROR: bool, - SEARCHER_PATH: str, SEARCHER_KWARGS: dict, } for param, value in settings.items(): diff --git a/tests/test_neps_api/testing_scripts/user_yaml_neps.py b/tests/test_neps_api/testing_scripts/user_yaml_neps.py index 6a93c4bd..03fd2afc 100644 --- a/tests/test_neps_api/testing_scripts/user_yaml_neps.py +++ b/tests/test_neps_api/testing_scripts/user_yaml_neps.py @@ -19,13 +19,12 @@ def run_pipeline(val1, val2): # Testing using created yaml with api script_directory = os.path.dirname(os.path.abspath(__file__)) parent_directory = os.path.join(script_directory, os.pardir) -searcher_path = os.path.join(parent_directory, "testing_yaml") +searcher_path = os.path.join(parent_directory, "testing_yaml/optimizer_test") neps.run( run_pipeline=run_pipeline, pipeline_space=pipeline_space, root_directory="user_yaml_bo", max_evaluations_total=1, - searcher="optimizer_test", - searcher_path=searcher_path, + searcher=searcher_path, initial_design_size=5, ) diff --git a/tests/test_yaml_run_args/run_args_empty.yaml b/tests/test_yaml_run_args/run_args_empty.yaml index 6153c520..fdd3a69c 100644 --- a/tests/test_yaml_run_args/run_args_empty.yaml +++ b/tests/test_yaml_run_args/run_args_empty.yaml @@ -19,7 +19,6 @@ parallelization_setup: search: searcher: - searcher_path: searcher_kwargs: initial_design_size: surrogate_model: diff --git a/tests/test_yaml_run_args/run_args_full.yaml b/tests/test_yaml_run_args/run_args_full.yaml index e9dc08eb..848a5454 100644 --- a/tests/test_yaml_run_args/run_args_full.yaml +++ b/tests/test_yaml_run_args/run_args_full.yaml @@ -25,7 +25,6 @@ error_handling: search: searcher: "bayesian_optimization" - searcher_path: "/path/to/model" searcher_kwargs: initial_design_size: 5 surrogate_model: gp diff --git a/tests/test_yaml_run_args/run_args_full_same_level.yaml b/tests/test_yaml_run_args/run_args_full_same_level.yaml index 9e32b64d..c855eb9a 100644 --- a/tests/test_yaml_run_args/run_args_full_same_level.yaml +++ b/tests/test_yaml_run_args/run_args_full_same_level.yaml @@ -15,7 +15,6 @@ loss_value_on_error: 2.4 cost_value_on_error: 2.1 ignore_errors: False searcher: "bayesian_optimization" -searcher_path: "/path/to/searcher" searcher_kwargs: initial_design_size: 5 surrogate_model: gp diff --git a/tests/test_yaml_run_args/run_args_invalid_key.yaml b/tests/test_yaml_run_args/run_args_invalid_key.yaml index 6fc840ce..450dbd6d 100644 --- a/tests/test_yaml_run_args/run_args_invalid_key.yaml +++ b/tests/test_yaml_run_args/run_args_invalid_key.yaml @@ -25,7 +25,6 @@ error_handling: search: searcher: "bayesian_optimization" - searcher_path: "/path/to/model" searcher_kwargs: initial_design_size: 5 surrogate_model: gp diff --git a/tests/test_yaml_run_args/run_args_invalid_type.yaml b/tests/test_yaml_run_args/run_args_invalid_type.yaml index f0e819a3..357f2a7b 100644 --- a/tests/test_yaml_run_args/run_args_invalid_type.yaml +++ b/tests/test_yaml_run_args/run_args_invalid_type.yaml @@ -22,7 +22,6 @@ error_handling: search: searcher: "bayesian_optimization" - searcher_path: searcher_kwargs: initial_design_size: 5 surrogate_model: gp diff --git a/tests/test_yaml_run_args/run_args_key_missing.yaml b/tests/test_yaml_run_args/run_args_key_missing.yaml index 704d1a2e..c36b0cbb 100644 --- a/tests/test_yaml_run_args/run_args_key_missing.yaml +++ b/tests/test_yaml_run_args/run_args_key_missing.yaml @@ -15,7 +15,6 @@ loss_value_on_error: 2.4 cost_value_on_error: 2.1 ignore_errors: False searcher: "bayesian_optimization" -searcher_path: "/path/to/searcher" searcher_kwargs: initial_design_size: 5 surrogate_model: gp diff --git a/tests/test_yaml_run_args/run_args_optional_loading_format.yaml b/tests/test_yaml_run_args/run_args_optional_loading_format.yaml index 9c447806..e9acaa08 100644 --- a/tests/test_yaml_run_args/run_args_optional_loading_format.yaml +++ b/tests/test_yaml_run_args/run_args_optional_loading_format.yaml @@ -19,7 +19,6 @@ ignore_errors: False searcher: # Optional Loading path: "neps/optimizers/bayesian_optimization/optimizer.py" name: BayesianOptimization -searcher_path: "/path/to/searcher" searcher_kwargs: initial_design_size: 5 surrogate_model: gp diff --git a/tests/test_yaml_run_args/run_args_partial.yaml b/tests/test_yaml_run_args/run_args_partial.yaml index 2137bce6..aa84460f 100644 --- a/tests/test_yaml_run_args/run_args_partial.yaml +++ b/tests/test_yaml_run_args/run_args_partial.yaml @@ -20,7 +20,6 @@ parallelization_setup: search: searcher: "bayesian_optimization" - searcher_path: searcher_kwargs: initial_design_size: 5 surrogate_model: gp diff --git a/tests/test_yaml_run_args/run_args_partial_same_level.yaml b/tests/test_yaml_run_args/run_args_partial_same_level.yaml index 614e8923..85cc1cbe 100644 --- a/tests/test_yaml_run_args/run_args_partial_same_level.yaml +++ b/tests/test_yaml_run_args/run_args_partial_same_level.yaml @@ -11,5 +11,4 @@ continue_until_max_evaluation_completed: True loss_value_on_error: None ignore_errors: True searcher: -searcher_path: pre_load_hooks: None diff --git a/tests/test_yaml_run_args/run_args_wrong_name.yaml b/tests/test_yaml_run_args/run_args_wrong_name.yaml index c0b451c4..1d90a140 100644 --- a/tests/test_yaml_run_args/run_args_wrong_name.yaml +++ b/tests/test_yaml_run_args/run_args_wrong_name.yaml @@ -25,7 +25,6 @@ error_handling: search: searcher: "bayesian_optimization" - searcher_path: "/path/to/model" searcher_kwargs: initial_design_size: 5 surrogate_model: gp diff --git a/tests/test_yaml_run_args/run_args_wrong_path.yaml b/tests/test_yaml_run_args/run_args_wrong_path.yaml index 95254e69..0bd57a72 100644 --- a/tests/test_yaml_run_args/run_args_wrong_path.yaml +++ b/tests/test_yaml_run_args/run_args_wrong_path.yaml @@ -25,7 +25,6 @@ error_handling: search: searcher: "bayesian_optimization" - searcher_path: "/path/to/model" searcher_kwargs: initial_design_size: 5 surrogate_model: gp diff --git a/tests/test_yaml_run_args/test_yaml_run_args.py b/tests/test_yaml_run_args/test_yaml_run_args.py index 6ebb7ddc..71b236ce 100644 --- a/tests/test_yaml_run_args/test_yaml_run_args.py +++ b/tests/test_yaml_run_args/test_yaml_run_args.py @@ -112,7 +112,6 @@ def are_functions_equivalent(f1: Union[Callable, List[Callable]], "cost_value_on_error": 3.7, "ignore_errors": True, "searcher": "bayesian_optimization", - "searcher_path": "/path/to/model", "searcher_kwargs": {"initial_design_size": 5, "surrogate_model": "gp"}, "pre_load_hooks": [hook1, hook2], }, @@ -135,7 +134,6 @@ def are_functions_equivalent(f1: Union[Callable, List[Callable]], "cost_value_on_error": 2.1, "ignore_errors": False, "searcher": "bayesian_optimization", - "searcher_path": "/path/to/searcher", "searcher_kwargs": {"initial_design_size": 5, "surrogate_model": "gp"}, "pre_load_hooks": [hook1], }, @@ -181,7 +179,6 @@ def are_functions_equivalent(f1: Union[Callable, List[Callable]], "cost_value_on_error": 2.1, "ignore_errors": False, "searcher": BayesianOptimization, - "searcher_path": "/path/to/searcher", "searcher_kwargs": { "initial_design_size": 5, "surrogate_model": "gp" From 3d554003179dc90d8d1a6e9a1a84a608df46af64 Mon Sep 17 00:00:00 2001 From: Daniel <63580393+danrgll@users.noreply.github.com> Date: Fri, 7 Jun 2024 14:04:02 +0200 Subject: [PATCH 10/29] fix pre-commit error --- neps/utils/common.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/neps/utils/common.py b/neps/utils/common.py index c362c855..6b6f43eb 100644 --- a/neps/utils/common.py +++ b/neps/utils/common.py @@ -193,8 +193,8 @@ def get_initial_directory(pipeline_directory: Path | str | None = None) -> Path: def get_searcher_data( - searcher: str | Path, loading_custom_searcher: bool = False -) -> (dict[str, Any], str): + searcher: str | Path, *, loading_custom_searcher: bool = False +) -> tuple[dict[str, Any], str]: """Returns the data from the YAML file associated with the specified searcher. Args: From 50d343e720349edc4b461d1038f6ef344bcc0410 Mon Sep 17 00:00:00 2001 From: Daniel <63580393+danrgll@users.noreply.github.com> Date: Thu, 13 Jun 2024 11:32:45 +0200 Subject: [PATCH 11/29] merge master + fix loading neps-searchers from yaml --- neps/api.py | 18 +++++------------- neps/search_spaces/yaml_search_space_utils.py | 2 +- neps/utils/common.py | 2 +- neps/utils/run_args_from_yaml.py | 5 ++--- 4 files changed, 9 insertions(+), 18 deletions(-) diff --git a/neps/api.py b/neps/api.py index 83a2f16c..1e5a3ca0 100644 --- a/neps/api.py +++ b/neps/api.py @@ -9,7 +9,8 @@ from typing import Callable, Iterable, Literal import ConfigSpace as CS -from neps.utils.run_args_from_yaml import check_essential_arguments, get_run_args_from_yaml,\ +from neps.utils.run_args_from_yaml import check_essential_arguments, \ + get_run_args_from_yaml, \ check_double_reference from neps.utils.common import instance_from_map @@ -25,17 +26,7 @@ from neps.status.status import post_run_csv from neps.utils.common import get_searcher_data, get_value from neps.utils.data_loading import _get_loss - -VALID_SEARCHER = [ - "default", - "bayesian_optimization", - "random_search", - "hyperband", - "priorband", - "mobster", - "asha", - "regularized_evolution", -] +from neps.optimizers.info import SearcherConfigs def _post_evaluation_hook_function( @@ -418,7 +409,8 @@ def _run_args( message = f"The pipeline_space has invalid type: {type(pipeline_space)}" raise TypeError(message) from e - if isinstance(searcher, (str, Path)) and searcher not in VALID_SEARCHER: + if isinstance(searcher, (str, Path)) and searcher not in \ + SearcherConfigs.get_searchers() and searcher is not "default": # The users has their own custom searcher. logging.info("Preparing to run user created searcher") diff --git a/neps/search_spaces/yaml_search_space_utils.py b/neps/search_spaces/yaml_search_space_utils.py index 27c268e4..d3f67a83 100644 --- a/neps/search_spaces/yaml_search_space_utils.py +++ b/neps/search_spaces/yaml_search_space_utils.py @@ -2,7 +2,7 @@ import logging import re -logger = logging.getLogger(__name__) +logger = logging.getLogger("neps") def convert_scientific_notation(value: str | int | float, show_usage_flag=False) \ diff --git a/neps/utils/common.py b/neps/utils/common.py index 6b6f43eb..f80a01ea 100644 --- a/neps/utils/common.py +++ b/neps/utils/common.py @@ -217,7 +217,7 @@ def get_searcher_data( data = yaml.safe_load(file) file_name = user_yaml_path.stem - searcher = data.get("name", file_name) + searcher = data.pop("name", file_name) else: # TODO(eddiebergman): This is a bad idea as it relies on folder structure to be diff --git a/neps/utils/run_args_from_yaml.py b/neps/utils/run_args_from_yaml.py index 3ef4a383..7f059947 100644 --- a/neps/utils/run_args_from_yaml.py +++ b/neps/utils/run_args_from_yaml.py @@ -97,9 +97,8 @@ def get_run_args_from_yaml(path: str) -> dict: check_run_args(settings) logger.debug( - f"'run_args' are extracted and type-tested from the referenced YAML file. " - f"These arguments will now be overwritten: {settings}." - ) + f"The 'run_args' arguments: {settings} are now extracted and type-tested from " + f"referenced YAML.") return settings From 3b4769ab8112e7039af2ae9030617db39d9a167a Mon Sep 17 00:00:00 2001 From: Daniel <63580393+danrgll@users.noreply.github.com> Date: Thu, 13 Jun 2024 11:50:14 +0200 Subject: [PATCH 12/29] update docs to the new design --- docs/reference/optimizers.md | 20 +++++++++----------- docs/reference/pipeline_space.md | 2 +- 2 files changed, 10 insertions(+), 12 deletions(-) diff --git a/docs/reference/optimizers.md b/docs/reference/optimizers.md index 927ebe14..cc779391 100644 --- a/docs/reference/optimizers.md +++ b/docs/reference/optimizers.md @@ -53,13 +53,14 @@ For more optimizers, please refer [here](#list-available-searchers) . For users who want more control over the optimizer's hyperparameters, you can create your own YAML configuration file. In this file, you can specify the hyperparameters for your preferred optimizer. To use this custom configuration, -provide the path to your YAML file using the `searcher_path` parameter when running the optimizer. +provide the path to your YAML file using the `searcher` parameter when running the optimizer. The library will then load your custom settings and use them for optimization. Here's the format of a custom YAML (`custom_bo.yaml`) configuration using `Bayesian Optimization` as an example: ```yaml algorithm: bayesian_optimization +name: my_custom_bo # # optional; otherwise, your searcher will be named after your YAML file, here 'custom_bo'. # Specific arguments depending on the searcher initial_design_size: 7 surrogate_model: gp @@ -78,16 +79,16 @@ neps.run( pipeline_space=pipeline_space, root_directory="results/", max_evaluations_total=25, - # searcher specified, along with an argument - searcher_path = "custom/path/to/directory" - # `custom_bo.yaml` should be in `searcher_path` - searcher="custom_bo", + searcher="path/to/custom_bo.yaml", ) ``` ### 4. Hyperparameter Overrides -If you want to make on-the-fly adjustments to the optimizer's hyperparameters without modifying the YAML configuration file, you can do so by passing keyword arguments (kwargs) to the neps.run function itself. This enables you to fine-tune specific hyperparameters without the need for YAML file updates. Any hyperparameter values provided as kwargs will take precedence over those specified in the YAML configuration. +If you want to make on-the-fly adjustments to the optimizer's hyperparameters without modifying the YAML configuration +file, you can do so by passing keyword arguments (kwargs) to the neps.run function itself. This enables you to fine-tune +specific hyperparameters without the need for YAML file updates. Any hyperparameter values provided as kwargs will take +precedence over those specified in the YAML configuration. ```python neps.run( @@ -95,12 +96,9 @@ neps.run( pipeline_space=pipeline_space, root_directory="results/", max_evaluations_total=25, - # searcher specified, along with an argument - searcher_path = "custom/path/to/directory" - # `custom_bo.yaml` should be in `searcher_path` - searcher="custom_bo", + searcher="path/to/custom_bo.yaml", initial_design_size=5, # overrides value in custom_bo.yaml - random_interleave_prob: 0.25 # overrides value in custom_bo.yaml + random_interleave_prob=0.25 # overrides value in custom_bo.yaml ) ``` diff --git a/docs/reference/pipeline_space.md b/docs/reference/pipeline_space.md index 1e52199e..796cd1e1 100644 --- a/docs/reference/pipeline_space.md +++ b/docs/reference/pipeline_space.md @@ -124,7 +124,7 @@ the NePS will automatically infer the data type based on the value provided. * If `lower` and `upper` are provided, then if they are both integers, the type will be inferred as `int`, otherwise as `float`. You can provide scientific notation for floating-point numbers as well. * If `choices` are provided, the type will be inferred as `categorical`. -* If `value` is provided, the type will be inferred as `constant`. +* If just a numeric or string is provided, the type will be inferred as `constant`. If none of these hold, an error will be raised. From 85c9fa5beee9df3d254dbc15825a819fb0b82422 Mon Sep 17 00:00:00 2001 From: Daniel <63580393+danrgll@users.noreply.github.com> Date: Thu, 13 Jun 2024 14:10:33 +0200 Subject: [PATCH 13/29] define dict in run_args yaml --- neps/api.py | 7 +- neps/utils/run_args_from_yaml.py | 99 ++++++++----------- .../customizing_neps_optimizer.yaml | 13 ++- .../test_declarative_usage_docs.py | 3 +- 4 files changed, 60 insertions(+), 62 deletions(-) diff --git a/neps/api.py b/neps/api.py index 1e5a3ca0..83b69211 100644 --- a/neps/api.py +++ b/neps/api.py @@ -314,7 +314,7 @@ def run( # Check to verify if the target directory contains history of another optimizer state # This check is performed only when the `searcher` is built during the run - if not isinstance(searcher, (BaseOptimizer, str)): + if not isinstance(searcher, (BaseOptimizer, str, dict)): raise ValueError( f"Unrecognized `searcher` of type {type(searcher)}. Not str or BaseOptimizer." ) @@ -417,6 +417,11 @@ def _run_args( config, searcher = get_searcher_data(searcher, loading_custom_searcher=True) searcher_info["searcher_selection"] = "user-yaml" searcher_info["neps_decision_tree"] = False + elif isinstance(searcher, dict): + config = searcher + searcher = config.pop("name", "unnamed-custom-searcher") + searcher_info["searcher_selection"] = "user-yaml" + searcher_info["neps_decision_tree"] = False else: if searcher in ["default", None]: # NePS decides the searcher according to the pipeline space. diff --git a/neps/utils/run_args_from_yaml.py b/neps/utils/run_args_from_yaml.py index 7f059947..94bcad5c 100644 --- a/neps/utils/run_args_from_yaml.py +++ b/neps/utils/run_args_from_yaml.py @@ -6,6 +6,7 @@ from typing import Callable, Optional, Dict, Tuple, List, Any import inspect from neps.search_spaces.search_space import pipeline_space_from_yaml +from pathlib import Path logger = logging.getLogger("neps") @@ -189,11 +190,10 @@ def handle_special_argument_cases(settings: Dict, special_configs: Dict) -> None - None: The function modifies 'settings' in place. """ - # Load the value of each key from a dictionary specifying "path" and "name". - process_config_key( - settings, special_configs, [SEARCHER, RUN_PIPELINE] - ) + # process special configs + process_run_pipeline(RUN_PIPELINE, special_configs, settings) process_pipeline_space(PIPELINE_SPACE, special_configs, settings) + process_searcher(SEARCHER, special_configs, settings) if special_configs[SEARCHER_KWARGS] is not None: configs = {} # Check if values of keys is not None and then add the dict to settings @@ -209,55 +209,7 @@ def handle_special_argument_cases(settings: Dict, special_configs: Dict) -> None settings[PRE_LOAD_HOOKS] = load_hooks_from_config(special_configs[PRE_LOAD_HOOKS]) -def process_config_key(settings: Dict, special_configs: Dict, keys: List) -> None: - """ - Enhance 'settings' by adding keys and their corresponding values or loaded objects - from 'special_configs'. Keys in 'special_configs' are processed to directly insert - their values into 'settings' or to load functions/objects using 'path' and 'name'. - Key handling varies: 'RUN_PIPELINE' requires a dictionary defining a loadable function - , whereas other keys may accept either strings or dictionaries - - Parameters: - - settings (dict): Dictionary to update. - - special_configs (dict): Contains keys and values for processing. - - keys (list): List of keys to process in 'special_configs'. - - Raises: - - KeyError: Missing 'path'/'name' for dictionaries. - - TypeError: Incorrect type for key's value; RUN_PIPELINE must be a dict, - others can be dict or string. - """ - for key in keys: - if special_configs.get(key) is not None: - value = special_configs[key] - if isinstance(value, str) and key != RUN_PIPELINE: - # searcher can be a string - settings[key] = value - elif isinstance(value, dict): - # dict that should contain 'path' and 'name' for loading value - try: - func = load_and_return_object(value["path"], value["name"], key) - settings[key] = func - except KeyError as e: - raise KeyError( - f"Missing key for argument {key}: {e}. Expect 'path' " - f"and 'name' as keys when loading '{key}' " - f"from 'run_args'" - ) from e - else: - if key == RUN_PIPELINE: - raise TypeError( - f"Value for {key} must be a dictionary, but got " - f"{type(value).__name__}." - ) - else: - raise TypeError( - f"Value for {key} must be a string or a dictionary, " - f"but got {type(value).__name__}." - ) - - -def process_pipeline_space(key, special_configs, settings): +def process_pipeline_space(key: str, special_configs: Dict, settings: Dict): """ Process or load the pipeline space configuration. @@ -289,19 +241,54 @@ def process_pipeline_space(key, special_configs, settings): else: # pipeline_space stored in a python dict, not using a yaml processed_pipeline_space = load_and_return_object(pipeline_space["path"], - pipeline_space[ - "name"], PIPELINE_SPACE) + pipeline_space["name"], + key) elif isinstance(pipeline_space, str): # load yaml from path processed_pipeline_space = pipeline_space_from_yaml(pipeline_space) else: raise TypeError( - f"Value for {PIPELINE_SPACE} must be a string or a dictionary, " + f"Value for {key} must be a string or a dictionary, " f"but got {type(pipeline_space).__name__}." ) settings[key] = processed_pipeline_space +def process_searcher(key: str, special_configs: Dict, settings: Dict): + if special_configs.get(key) is not None: + searcher = special_configs[key] + if isinstance(searcher, dict): + # determine if dict contains path_loading or the actual searcher config + expected_keys = {"path", "name"} + actual_keys = set(searcher.keys()) + if expected_keys == actual_keys: + searcher = load_and_return_object(searcher["path"], + searcher[ + "name"], key) + elif isinstance(searcher, (str, Path)): + pass + else: + raise TypeError( + f"Value for {key} must be a string or a dictionary, " + f"but got {type(searcher).__name__}." + ) + settings[key] = searcher + + +def process_run_pipeline(key: str, special_configs: Dict, settings: Dict): + if special_configs.get(key) is not None: + config = special_configs[key] + try: + func = load_and_return_object(config["path"], config["name"], key) + settings[key] = func + except KeyError as e: + raise KeyError( + f"Missing key for argument {key}: {e}. Expect 'path' " + f"and 'name' as keys when loading '{key}' " + f"from 'run_args'" + ) from e + + def load_and_return_object(module_path: str, object_name: str, key: str) -> object: """ Dynamically loads an object from a given module file path. diff --git a/tests/test_yaml_run_args/test_declarative_usage_docs/customizing_neps_optimizer.yaml b/tests/test_yaml_run_args/test_declarative_usage_docs/customizing_neps_optimizer.yaml index b079193e..482e630c 100644 --- a/tests/test_yaml_run_args/test_declarative_usage_docs/customizing_neps_optimizer.yaml +++ b/tests/test_yaml_run_args/test_declarative_usage_docs/customizing_neps_optimizer.yaml @@ -1,17 +1,21 @@ run_pipeline: - path: path/to/your/run_pipeline.py # Path to the function file - name: example_pipeline # Function name within the file + path: tests/test_yaml_run_args/test_declarative_usage_docs/run_pipeline.py + name: run_pipeline_constant pipeline_space: learning_rate: lower: 1e-5 upper: 1e-1 log: True # Log scale for learning rate + epochs: + lower: 5 + upper: 20 + is_fidelity: True optimizer: choices: [adam, sgd, adamw] - epochs: 50 + batch_size: 64 -root_directory: path/to/results # Directory for result storage +root_directory: path/to/results/custominizing_neps_optimizer # Directory for result storage max_evaluations_total: 20 # Budget searcher: algorithm: bayesian_optimization # name linked with neps keywords, more information click here..? @@ -22,3 +26,4 @@ searcher: acquisition_sampler: random random_interleave_prob: 0.1 +overwrite_working_directory: True diff --git a/tests/test_yaml_run_args/test_declarative_usage_docs/test_declarative_usage_docs.py b/tests/test_yaml_run_args/test_declarative_usage_docs/test_declarative_usage_docs.py index ad6d94f7..aa7ab8ee 100644 --- a/tests/test_yaml_run_args/test_declarative_usage_docs/test_declarative_usage_docs.py +++ b/tests/test_yaml_run_args/test_declarative_usage_docs/test_declarative_usage_docs.py @@ -9,7 +9,8 @@ @pytest.mark.parametrize("yaml_file", [ "simple_example_including_run_pipeline.yaml", "full_configuration_template.yaml", - "defining_hooks.yaml" + "defining_hooks.yaml", + "customizing_neps_optimizer.yaml" ]) def test_run_with_yaml(yaml_file: str) -> None: """ From f17b1f1deb56c4a2d5148170db272a5b3f220c87 Mon Sep 17 00:00:00 2001 From: Daniel <63580393+danrgll@users.noreply.github.com> Date: Fri, 14 Jun 2024 09:58:26 +0200 Subject: [PATCH 14/29] change searcher key algorithm to strategy + rm searcher_kwargs argument --- neps/api.py | 26 ++- neps/optimizers/default_searchers/asha.yaml | 2 +- .../default_searchers/asha_prior.yaml | 2 +- .../bayesian_optimization.yaml | 2 +- .../default_searchers/hyperband.yaml | 2 +- .../optimizers/default_searchers/mobster.yaml | 2 +- neps/optimizers/default_searchers/pibo.yaml | 2 +- .../default_searchers/priorband.yaml | 2 +- .../default_searchers/priorband_bo.yaml | 2 +- .../default_searchers/random_search.yaml | 2 +- .../regularized_evolution.yaml | 2 +- .../default_searchers/successive_halving.yaml | 2 +- .../successive_halving_prior.yaml | 2 +- .../{run_args_from_yaml.py => run_args.py} | 174 ++++++++++-------- neps_examples/yaml_usage/config.yaml | 30 +++ neps_examples/yaml_usage/hpo_example.py | 9 +- neps_examples/yaml_usage/pipeline_space.yaml | 15 -- neps_examples/yaml_usage/run_args.yaml | 14 -- .../yaml_usage/run_args_alternative.yaml | 62 ------- .../testing_yaml/optimizer_test.yaml | 2 +- tests/test_yaml_run_args/run_args_empty.yaml | 3 - tests/test_yaml_run_args/run_args_full.yaml | 4 +- .../run_args_full_same_level.yaml | 4 +- .../run_args_invalid_key.yaml | 4 +- .../run_args_invalid_type.yaml | 4 +- .../run_args_key_missing.yaml | 4 +- .../run_args_optional_loading_format.yaml | 1 - .../test_yaml_run_args/run_args_partial.yaml | 4 +- .../run_args_wrong_name.yaml | 4 +- .../run_args_wrong_path.yaml | 4 +- .../customizing_neps_optimizer.yaml | 2 +- .../loading_own_optimizer.yaml | 10 +- .../set_up_optimizer.yaml | 2 +- .../test_declarative_usage_docs.py | 3 +- .../test_run_args_by_neps_run/config.yaml | 9 +- .../loading_pipeline_space.yaml | 4 +- .../test_yaml_run_args/test_yaml_run_args.py | 2 +- 37 files changed, 196 insertions(+), 228 deletions(-) rename neps/utils/{run_args_from_yaml.py => run_args.py} (80%) create mode 100644 neps_examples/yaml_usage/config.yaml delete mode 100644 neps_examples/yaml_usage/pipeline_space.yaml delete mode 100644 neps_examples/yaml_usage/run_args.yaml delete mode 100644 neps_examples/yaml_usage/run_args_alternative.yaml diff --git a/neps/api.py b/neps/api.py index 83b69211..82f2a3cd 100644 --- a/neps/api.py +++ b/neps/api.py @@ -9,7 +9,7 @@ from typing import Callable, Iterable, Literal import ConfigSpace as CS -from neps.utils.run_args_from_yaml import check_essential_arguments, \ +from neps.utils.run_args import check_essential_arguments, \ get_run_args_from_yaml, \ check_double_reference @@ -441,14 +441,32 @@ def _run_args( # Fetching the searcher data, throws an error when the searcher is not found config, searcher = get_searcher_data(searcher) + # Check for deprecated 'algorithm' argument if "algorithm" in config: - searcher_alg = config.pop("algorithm") + warnings.warn( + "The 'algorithm' argument is deprecated and will be removed in future versions. Please use 'strategy' instead.", + DeprecationWarning + ) + # Map the old 'algorithm' argument to 'strategy' + config['strategy'] = config.pop("algorithm") + + # Check for deprecated 'algorithm' argument + if "algorithm" in config: + warnings.warn( + "The 'algorithm' argument is deprecated and will be removed in future versions. Please use 'strategy' instead.", + DeprecationWarning + ) + # Map the old 'algorithm' argument to 'strategy' + config['strategy'] = config.pop("algorithm") + + if "strategy" in config: + searcher_alg = config.pop("strategy") else: - raise KeyError(f"Missing key algorithm in searcher config:{config}") + raise KeyError(f"Missing key strategy in searcher config:{config}") searcher_config = config logger.info(f"Running {searcher} as the searcher") - logger.info(f"Algorithm: {searcher_alg}") + logger.info(f"Strategy: {searcher_alg}") # Used to create the yaml holding information about the searcher. # Also important for testing and debugging the api. diff --git a/neps/optimizers/default_searchers/asha.yaml b/neps/optimizers/default_searchers/asha.yaml index 8d914ad4..0b140484 100644 --- a/neps/optimizers/default_searchers/asha.yaml +++ b/neps/optimizers/default_searchers/asha.yaml @@ -1,4 +1,4 @@ -algorithm: asha +strategy: asha # Arguments that can be modified by the user eta: 3 early_stopping_rate: 0 diff --git a/neps/optimizers/default_searchers/asha_prior.yaml b/neps/optimizers/default_searchers/asha_prior.yaml index dc6852d1..95bacb6c 100644 --- a/neps/optimizers/default_searchers/asha_prior.yaml +++ b/neps/optimizers/default_searchers/asha_prior.yaml @@ -1,4 +1,4 @@ -algorithm: asha_prior +strategy: asha_prior # Arguments that can be modified by the user eta: 3 early_stopping_rate: 0 diff --git a/neps/optimizers/default_searchers/bayesian_optimization.yaml b/neps/optimizers/default_searchers/bayesian_optimization.yaml index 26d2c507..cf3717ab 100644 --- a/neps/optimizers/default_searchers/bayesian_optimization.yaml +++ b/neps/optimizers/default_searchers/bayesian_optimization.yaml @@ -1,4 +1,4 @@ -algorithm: bayesian_optimization +strategy: bayesian_optimization # Arguments that can be modified by the user initial_design_size: 10 surrogate_model: gp # or {"gp_hierarchy"} diff --git a/neps/optimizers/default_searchers/hyperband.yaml b/neps/optimizers/default_searchers/hyperband.yaml index d39fb139..b560af48 100644 --- a/neps/optimizers/default_searchers/hyperband.yaml +++ b/neps/optimizers/default_searchers/hyperband.yaml @@ -1,4 +1,4 @@ -algorithm: hyperband +strategy: hyperband # Arguments that can be modified by the user eta: 3 initial_design_type: max_budget diff --git a/neps/optimizers/default_searchers/mobster.yaml b/neps/optimizers/default_searchers/mobster.yaml index 385b74ad..9ce821b3 100644 --- a/neps/optimizers/default_searchers/mobster.yaml +++ b/neps/optimizers/default_searchers/mobster.yaml @@ -1,4 +1,4 @@ -algorithm: mobster +strategy: mobster # Arguments that can be modified by the user eta: 3 initial_design_type: max_budget diff --git a/neps/optimizers/default_searchers/pibo.yaml b/neps/optimizers/default_searchers/pibo.yaml index b75a23f3..8274ea4b 100644 --- a/neps/optimizers/default_searchers/pibo.yaml +++ b/neps/optimizers/default_searchers/pibo.yaml @@ -1,4 +1,4 @@ -algorithm: bayesian_optimization +strategy: bayesian_optimization # Arguments that can be modified by the user initial_design_size: 10 surrogate_model: gp # or {"gp_hierarchy"} diff --git a/neps/optimizers/default_searchers/priorband.yaml b/neps/optimizers/default_searchers/priorband.yaml index f5a16f6f..5d9dac86 100644 --- a/neps/optimizers/default_searchers/priorband.yaml +++ b/neps/optimizers/default_searchers/priorband.yaml @@ -1,4 +1,4 @@ -algorithm: priorband +strategy: priorband # Arguments that can be modified by the user eta: 3 initial_design_type: max_budget diff --git a/neps/optimizers/default_searchers/priorband_bo.yaml b/neps/optimizers/default_searchers/priorband_bo.yaml index 134ff78c..5a9fd3a9 100644 --- a/neps/optimizers/default_searchers/priorband_bo.yaml +++ b/neps/optimizers/default_searchers/priorband_bo.yaml @@ -1,4 +1,4 @@ -algorithm: priorband +strategy: priorband # Arguments that can be modified by the user eta: 3 initial_design_type: max_budget diff --git a/neps/optimizers/default_searchers/random_search.yaml b/neps/optimizers/default_searchers/random_search.yaml index 848a7873..e7e8879c 100644 --- a/neps/optimizers/default_searchers/random_search.yaml +++ b/neps/optimizers/default_searchers/random_search.yaml @@ -1,4 +1,4 @@ -algorithm: random_search +strategy: random_search # Arguments that can be modified by the user use_priors: false ignore_fidelity: true diff --git a/neps/optimizers/default_searchers/regularized_evolution.yaml b/neps/optimizers/default_searchers/regularized_evolution.yaml index 0e298423..040c0b41 100644 --- a/neps/optimizers/default_searchers/regularized_evolution.yaml +++ b/neps/optimizers/default_searchers/regularized_evolution.yaml @@ -1,4 +1,4 @@ -algorithm: regularized_evolution +strategy: regularized_evolution # Arguments that can be modified by the user population_size: 30 sample_size: 10 diff --git a/neps/optimizers/default_searchers/successive_halving.yaml b/neps/optimizers/default_searchers/successive_halving.yaml index 148cbc69..d7d20c9f 100644 --- a/neps/optimizers/default_searchers/successive_halving.yaml +++ b/neps/optimizers/default_searchers/successive_halving.yaml @@ -1,4 +1,4 @@ -algorithm: successive_halving +strategy: successive_halving # Arguments that can be modified by the user eta: 3 early_stopping_rate: 0 diff --git a/neps/optimizers/default_searchers/successive_halving_prior.yaml b/neps/optimizers/default_searchers/successive_halving_prior.yaml index 29b320dd..7778fffd 100644 --- a/neps/optimizers/default_searchers/successive_halving_prior.yaml +++ b/neps/optimizers/default_searchers/successive_halving_prior.yaml @@ -1,4 +1,4 @@ -algorithm: successive_halving_prior +strategy: successive_halving_prior # Arguments that can be modified by the user eta: 3 early_stopping_rate: 0 diff --git a/neps/utils/run_args_from_yaml.py b/neps/utils/run_args.py similarity index 80% rename from neps/utils/run_args_from_yaml.py rename to neps/utils/run_args.py index 94bcad5c..c4f69ec0 100644 --- a/neps/utils/run_args_from_yaml.py +++ b/neps/utils/run_args.py @@ -1,12 +1,16 @@ +from __future__ import annotations + import importlib.util +import inspect import logging import sys +from pathlib import Path +from typing import Any, Callable + import yaml + from neps.optimizers.base_optimizer import BaseOptimizer -from typing import Callable, Optional, Dict, Tuple, List, Any -import inspect from neps.search_spaces.search_space import pipeline_space_from_yaml -from pathlib import Path logger = logging.getLogger("neps") @@ -32,8 +36,7 @@ def get_run_args_from_yaml(path: str) -> dict: - """ - Load and validate NEPS run arguments from a specified YAML configuration file + """Load and validate NEPS run arguments from a specified YAML configuration file provided via run_args. This function reads a YAML file, extracts the arguments required by NEPS, @@ -84,11 +87,13 @@ def get_run_args_from_yaml(path: str) -> dict: if parameter in expected_parameters: settings[parameter] = value else: - raise KeyError(f"Parameter '{parameter}' is not an argument of neps.run() " - f"provided via run_args." - f"See here all valid arguments:" - f" {', '.join(expected_parameters)}, " - f"'run_pipeline', 'preload_hooks', 'pipeline_space'") + raise KeyError( + f"Parameter '{parameter}' is not an argument of neps.run() " + f"provided via run_args." + f"See here all valid arguments:" + f" {', '.join(expected_parameters)}, " + f"'run_pipeline', 'preload_hooks', 'pipeline_space'" + ) # Process complex configurations (e.g., 'pipeline_space', 'searcher') and integrate # them into 'settings'. @@ -99,14 +104,14 @@ def get_run_args_from_yaml(path: str) -> dict: logger.debug( f"The 'run_args' arguments: {settings} are now extracted and type-tested from " - f"referenced YAML.") + f"referenced YAML." + ) return settings -def config_loader(path: str) -> Dict: - """ - Loads a YAML file and returns the contents under the 'run_args' key. +def config_loader(path: str) -> dict: + """Loads a YAML file and returns the contents under the 'run_args' key. Validates the existence and format of the YAML file and checks for the presence of the 'run_args' as the only top level key. If any conditions are not met, @@ -129,18 +134,19 @@ def config_loader(path: str) -> Dict: with open(path) as file: config = yaml.safe_load(file) except FileNotFoundError as e: - raise FileNotFoundError(f"The specified file was not found: '{path}'." - f" Please make sure that the path is correct and " - f"try again.") from e + raise FileNotFoundError( + f"The specified file was not found: '{path}'." + f" Please make sure that the path is correct and " + f"try again." + ) from e except yaml.YAMLError as e: raise ValueError(f"The file at {path} is not a valid YAML file.") from e return config -def extract_leaf_keys(d: Dict, special_keys: Dict = None) -> Tuple[Dict, Dict]: - """ - Recursive function to extract leaf keys and their values from a nested dictionary. +def extract_leaf_keys(d: dict, special_keys: dict | None = None) -> tuple[dict, dict]: + """Recursive function to extract leaf keys and their values from a nested dictionary. Special keys (e.g. 'searcher_kwargs', 'run_pipeline') are also extracted if present and their corresponding values (dict) at any level in the nested structure. @@ -171,9 +177,8 @@ def extract_leaf_keys(d: Dict, special_keys: Dict = None) -> Tuple[Dict, Dict]: return leaf_keys, special_keys -def handle_special_argument_cases(settings: Dict, special_configs: Dict) -> None: - """ - Process and integrate special configuration cases into the 'settings' dictionary. +def handle_special_argument_cases(settings: dict, special_configs: dict) -> None: + """Process and integrate special configuration cases into the 'settings' dictionary. This function updates 'settings' with values from 'special_configs'. It handles specific keys that require more complex processing, such as 'pipeline_space' and @@ -194,24 +199,14 @@ def handle_special_argument_cases(settings: Dict, special_configs: Dict) -> None process_run_pipeline(RUN_PIPELINE, special_configs, settings) process_pipeline_space(PIPELINE_SPACE, special_configs, settings) process_searcher(SEARCHER, special_configs, settings) - if special_configs[SEARCHER_KWARGS] is not None: - configs = {} - # Check if values of keys is not None and then add the dict to settings - # Increase flexibility to let value of a key in yaml file empty - for key, value in special_configs[SEARCHER_KWARGS].items(): - if value is not None: - configs[key] = value - if len(configs) != 0: - settings[SEARCHER_KWARGS] = configs if special_configs[PRE_LOAD_HOOKS] is not None: # Loads the pre_load_hooks functions and add them in a list to settings. settings[PRE_LOAD_HOOKS] = load_hooks_from_config(special_configs[PRE_LOAD_HOOKS]) -def process_pipeline_space(key: str, special_configs: Dict, settings: Dict): - """ - Process or load the pipeline space configuration. +def process_pipeline_space(key: str, special_configs: dict, settings: dict): + """Process or load the pipeline space configuration. This function checks if the given key exists in the `special_configs` dictionary. If it exists, it processes the associated value, which can be either a dictionary @@ -228,7 +223,7 @@ def process_pipeline_space(key: str, special_configs: Dict, settings: Dict): Raises: TypeError: If the value associated with the key is neither a string nor a dictionary. - """ + """ if special_configs.get(key) is not None: pipeline_space = special_configs[key] if isinstance(pipeline_space, dict): @@ -240,9 +235,9 @@ def process_pipeline_space(key: str, special_configs: Dict, settings: Dict): processed_pipeline_space = pipeline_space_from_yaml(pipeline_space) else: # pipeline_space stored in a python dict, not using a yaml - processed_pipeline_space = load_and_return_object(pipeline_space["path"], - pipeline_space["name"], - key) + processed_pipeline_space = load_and_return_object( + pipeline_space["path"], pipeline_space["name"], key + ) elif isinstance(pipeline_space, str): # load yaml from path processed_pipeline_space = pipeline_space_from_yaml(pipeline_space) @@ -254,7 +249,20 @@ def process_pipeline_space(key: str, special_configs: Dict, settings: Dict): settings[key] = processed_pipeline_space -def process_searcher(key: str, special_configs: Dict, settings: Dict): +def process_searcher(key: str, special_configs: dict, settings: dict): + """Processes the searcher configuration and updates the settings dictionary. + + Checks if the key exists in special_configs. If found, it processes the + value based on its type. Updates settings with the processed searcher. + + Parameters: + key (str): Key to look up in special_configs. + special_configs (dict): Dictionary of special configurations. + settings (dict): Dictionary to update with the processed searcher. + + Raises: + TypeError: If the value for the key is neither a string, Path, nor a dictionary. + """ if special_configs.get(key) is not None: searcher = special_configs[key] if isinstance(searcher, dict): @@ -262,9 +270,7 @@ def process_searcher(key: str, special_configs: Dict, settings: Dict): expected_keys = {"path", "name"} actual_keys = set(searcher.keys()) if expected_keys == actual_keys: - searcher = load_and_return_object(searcher["path"], - searcher[ - "name"], key) + searcher = load_and_return_object(searcher["path"], searcher["name"], key) elif isinstance(searcher, (str, Path)): pass else: @@ -275,7 +281,17 @@ def process_searcher(key: str, special_configs: Dict, settings: Dict): settings[key] = searcher -def process_run_pipeline(key: str, special_configs: Dict, settings: Dict): +def process_run_pipeline(key: str, special_configs: dict, settings: dict): + """Processes the run pipeline configuration and updates the settings dictionary. + + Parameters: + key (str): Key to look up in special_configs. + special_configs (dict): Dictionary of special configurations. + settings (dict): Dictionary to update with the processed function. + + Raises: + KeyError: If required keys ('path' and 'name') are missing in the config. + """ if special_configs.get(key) is not None: config = special_configs[key] try: @@ -290,8 +306,7 @@ def process_run_pipeline(key: str, special_configs: Dict, settings: Dict): def load_and_return_object(module_path: str, object_name: str, key: str) -> object: - """ - Dynamically loads an object from a given module file path. + """Dynamically loads an object from a given module file path. This function attempts to dynamically import an object by its name from a specified module path. If the initial import fails, it retries with a '.py' extension appended @@ -309,12 +324,16 @@ def load_and_return_object(module_path: str, object_name: str, key: str) -> obje Raises: ImportError: If the module or object cannot be found, with a message detailing the issue. - """ + """ + def import_object(path): try: # Convert file system path to module path, removing '.py' if present. - module_name = path[:-3].replace("/", ".") if path.endswith( - '.py') else path.replace("/", ".") + module_name = ( + path[:-3].replace("/", ".") + if path.endswith(".py") + else path.replace("/", ".") + ) # Dynamically import the module. spec = importlib.util.spec_from_file_location(module_name, path) @@ -337,21 +356,22 @@ def import_object(path): if imported_object is None: # If the object could not be imported, attempt again by appending '.py', # if not already present. - if not module_path.endswith('.py'): - module_path += '.py' + if not module_path.endswith(".py"): + module_path += ".py" imported_object = import_object(module_path) if imported_object is None: - raise ImportError(f"Failed to import '{object_name}' for argument '{key}'. " - f"Module path '{module_path}' not found or object does not " - f"exist.") + raise ImportError( + f"Failed to import '{object_name}' for argument '{key}'. " + f"Module path '{module_path}' not found or object does not " + f"exist." + ) return imported_object -def load_hooks_from_config(pre_load_hooks_dict: Dict) -> List: - """ - Loads hook functions from a dictionary of configurations. +def load_hooks_from_config(pre_load_hooks_dict: dict) -> list: + """Loads hook functions from a dictionary of configurations. Args: pre_load_hooks_dict (Dict): Dictionary with hook names as keys and paths as values @@ -366,9 +386,8 @@ def load_hooks_from_config(pre_load_hooks_dict: Dict) -> List: return loaded_hooks -def check_run_args(settings: Dict) -> None: - """ - Validates the types of NePS configuration settings. +def check_run_args(settings: dict) -> None: + """Validates the types of NePS configuration settings. Checks that each setting's value type matches its expected type. Raises TypeError for type mismatches. @@ -399,7 +418,7 @@ def check_run_args(settings: Dict) -> None: SEARCHER_KWARGS: dict, } for param, value in settings.items(): - if param == DEVELOPMENT_STAGE_ID or param == TASK_ID: + if param in (DEVELOPMENT_STAGE_ID, TASK_ID): # this argument can be Any continue elif param == PRE_LOAD_HOOKS: @@ -425,16 +444,15 @@ def check_run_args(settings: Dict) -> None: def check_essential_arguments( - run_pipeline: Optional[Callable], - root_directory: Optional[str], - pipeline_space: Optional[dict], - max_cost_total: Optional[int], - max_evaluation_total: Optional[int], - searcher: Optional[BaseOptimizer], - run_args: Optional[str], + run_pipeline: Callable | None, + root_directory: str | None, + pipeline_space: dict | None, + max_cost_total: int | None, + max_evaluation_total: int | None, + searcher: BaseOptimizer | None, + run_args: str | None, ) -> None: - """ - Validates essential NEPS configuration arguments. + """Validates essential NEPS configuration arguments. Ensures 'run_pipeline', 'root_directory', 'pipeline_space', and either 'max_cost_total' or 'max_evaluation_total' are provided for NePS execution. @@ -457,11 +475,10 @@ def check_essential_arguments( raise ValueError("'run_pipeline' is required but was not provided.") if not root_directory: raise ValueError("'root_directory' is required but was not provided.") - if not pipeline_space: + if not pipeline_space and (run_args or not isinstance(searcher, BaseOptimizer)): # handling special case for searcher instance, in which user doesn't have to # provide the search_space because it's the argument of the searcher. - if run_args or not isinstance(searcher, BaseOptimizer): - raise ValueError("'pipeline_space' is required but was not provided.") + raise ValueError("'pipeline_space' is required but was not provided.") if not max_evaluation_total and not max_cost_total: raise ValueError( @@ -470,9 +487,10 @@ def check_essential_arguments( ) -def check_double_reference(func: Callable, func_arguments: Dict, yaml_arguments: Dict) -> Any: - """ - Checks if no argument is defined both via function arguments and YAML. +def check_double_reference( + func: Callable, func_arguments: dict, yaml_arguments: dict +) -> Any: + """Checks if no argument is defined both via function arguments and YAML. Parameters: - func (Callable): The function to check arguments against. @@ -492,10 +510,6 @@ def check_double_reference(func: Callable, func_arguments: Dict, yaml_arguments: if name == RUN_ARGS: # Ignoring run_args argument continue - if name == SEARCHER_KWARGS: - # searcher_kwargs does not have a default specified by inspect - if func_arguments[name] == {}: - continue if name in yaml_arguments: raise ValueError( f"Conflict for argument '{name}': Argument is defined both via " diff --git a/neps_examples/yaml_usage/config.yaml b/neps_examples/yaml_usage/config.yaml new file mode 100644 index 00000000..c8d1addc --- /dev/null +++ b/neps_examples/yaml_usage/config.yaml @@ -0,0 +1,30 @@ +experiment: + root_directory: "example_run" + max_evaluations_total: 20 + overwrite_working_directory: True + post_run_summary: True + development_stage_id: "beta" + +run_pipeline: + path: "hpo_example.py" + name: "training_pipeline" + +pipeline_space: + epochs: 5 + learning_rate: + lower: 1e-5 + upper: 1e-1 + log: True + num_layers: + lower: 1 + upper: 5 + optimizer: + choices: [ "adam", "sgd" ] + num_neurons: + lower: 64 + upper: 128 + +searcher: + strategy: "bayesian_optimization" + initial_design_size: 5 + surrogate_model: gp diff --git a/neps_examples/yaml_usage/hpo_example.py b/neps_examples/yaml_usage/hpo_example.py index ccea204c..4865d273 100644 --- a/neps_examples/yaml_usage/hpo_example.py +++ b/neps_examples/yaml_usage/hpo_example.py @@ -25,9 +25,9 @@ Usage: 1. Define model architecture and training logic in `SimpleNN` and `training_pipeline`. -2. Configure hyperparameters and optimization settings in 'run_args.yaml'. +2. Configure hyperparameters and optimization settings in 'config.yaml'. 3. Launch optimization with NePS by calling `neps.run`, specifying the training pipeline, - pipeline_space(pipeline_space.yaml) and configuration file(run_args.yaml). +and configuration file(config.yaml). """ @@ -122,7 +122,6 @@ def training_pipeline(num_layers, num_neurons, epochs, learning_rate, optimizer) logging.basicConfig(level=logging.INFO) # Run optimization using neps.run(...). Arguments can be provided directly to neps.run - # or defined in a configuration file (e.g., "run_args.yaml") passed through + # or defined in a configuration file (e.g., "config.yaml") passed through # the run_args parameter. - neps.run(run_args="run_args.yaml") - # neps.run(run_args="run_args_alternative.yaml") + neps.run(run_args="config.yaml") diff --git a/neps_examples/yaml_usage/pipeline_space.yaml b/neps_examples/yaml_usage/pipeline_space.yaml deleted file mode 100644 index bbc8715f..00000000 --- a/neps_examples/yaml_usage/pipeline_space.yaml +++ /dev/null @@ -1,15 +0,0 @@ -epochs: - lower: 1 - upper: 5 -learning_rate: - lower: 1e-5 - upper: 1e-1 - log: True -num_layers: - lower: 1 - upper: 5 -optimizer: - choices: ["adam", "sgd"] -num_neurons: - lower: 64 - upper: 128 diff --git a/neps_examples/yaml_usage/run_args.yaml b/neps_examples/yaml_usage/run_args.yaml deleted file mode 100644 index 89366f70..00000000 --- a/neps_examples/yaml_usage/run_args.yaml +++ /dev/null @@ -1,14 +0,0 @@ -run_args: - run_pipeline: - path: "hpo_example.py" - name: "training_pipeline" - pipeline_space: "pipeline_space.yaml" - root_directory: "example_run" - max_evaluations_total: 20 - overwrite_working_directory: True - post_run_summary: True - development_stage_id: "alpha" - searcher: "bayesian_optimization" - searcher_kwargs: - initial_design_size: 5 - surrogate_model: gp diff --git a/neps_examples/yaml_usage/run_args_alternative.yaml b/neps_examples/yaml_usage/run_args_alternative.yaml deleted file mode 100644 index 7c8ef9c7..00000000 --- a/neps_examples/yaml_usage/run_args_alternative.yaml +++ /dev/null @@ -1,62 +0,0 @@ -# You can create your own structure, ordering the keys for readability under substructures like 'budget' -# 'monitoring' and so on -run_args: - # Specify the file path for NePS to load your pipeline and the name of the function. - run_pipeline: - path: "hpo_example.py" - name: "training_pipeline" - # Provide the path from which NePS can load the pipeline_space. - pipeline_space: "pipeline_space.yaml" - - # Path for storing optimization progress/process. - root_directory: "example_run_alternative" - - budget: - # Caps the total number of configurations to test, controlling the experiment's length. - max_evaluations_total: 20 - # Sets a resource usage limit (e.g., computational time), stopping new evaluations once reached. - max_cost_total: - - monitoring: - # Clears root_directory at start, useful for clean-slate runs or debugging. - overwrite_working_directory: True - - # Generates a CSV summary post-optimization, detailing performance, best configs, and error counts. - post_run_summary: True - - # Tags results within root_directory for multi-stage projects, avoiding clutter. - development_stage_id: None - - # Similar to development_stage_id, but for categorizing tasks, aiding in results organization. - task_id: None - - parallelization_setup: - # Restricts evaluations per neps.run call, useful for managing resource allocation. - max_evaluations_per_run: - # Ensures continued sampling until a set number of evaluations finish, optimizing for time constraints. - continue_until_max_evaluation_completed: False - - error_handling: - # Assigns a default loss to failed evaluations, allowing the optimization to proceed. - loss_value_on_error: None - - # Assigns a default cost to failed evaluations, useful for budget management. - cost_value_on_error: None - - # Continues optimization past errors without stopping, counting them towards the total evaluation limit. - ignore_errors: False - - search: - # Chooses the optimization strategy, either from built-in options or via a custom BaseOptimizer. - searcher: "bayesian_optimization" - - # Specifies a path to a custom searcher algorithm, for advanced customization. - searcher_path: - - # Passes extra arguments to the searcher, tailoring its settings. - searcher_kwargs: - initial_design_size: 5 - surrogate_model: gp - - # List of functions to execute before loading results, for setup or preprocessing needs. - pre_load_hooks: diff --git a/tests/test_neps_api/testing_yaml/optimizer_test.yaml b/tests/test_neps_api/testing_yaml/optimizer_test.yaml index 9d7e27f7..f65af743 100644 --- a/tests/test_neps_api/testing_yaml/optimizer_test.yaml +++ b/tests/test_neps_api/testing_yaml/optimizer_test.yaml @@ -1,4 +1,4 @@ -algorithm: bayesian_optimization +strategy: bayesian_optimization # Specific arguments depending on the searcher initial_design_size: 7 surrogate_model: gp diff --git a/tests/test_yaml_run_args/run_args_empty.yaml b/tests/test_yaml_run_args/run_args_empty.yaml index fdd3a69c..8f47d179 100644 --- a/tests/test_yaml_run_args/run_args_empty.yaml +++ b/tests/test_yaml_run_args/run_args_empty.yaml @@ -19,8 +19,5 @@ parallelization_setup: search: searcher: - searcher_kwargs: - initial_design_size: - surrogate_model: pre_load_hooks: None diff --git a/tests/test_yaml_run_args/run_args_full.yaml b/tests/test_yaml_run_args/run_args_full.yaml index 848a5454..cc573995 100644 --- a/tests/test_yaml_run_args/run_args_full.yaml +++ b/tests/test_yaml_run_args/run_args_full.yaml @@ -24,8 +24,8 @@ error_handling: ignore_errors: True search: - searcher: "bayesian_optimization" - searcher_kwargs: + searcher: + strategy: "bayesian_optimization" initial_design_size: 5 surrogate_model: gp diff --git a/tests/test_yaml_run_args/run_args_full_same_level.yaml b/tests/test_yaml_run_args/run_args_full_same_level.yaml index c855eb9a..b6690a1f 100644 --- a/tests/test_yaml_run_args/run_args_full_same_level.yaml +++ b/tests/test_yaml_run_args/run_args_full_same_level.yaml @@ -14,8 +14,8 @@ continue_until_max_evaluation_completed: True loss_value_on_error: 2.4 cost_value_on_error: 2.1 ignore_errors: False -searcher: "bayesian_optimization" -searcher_kwargs: +searcher: + strategy: "bayesian_optimization" initial_design_size: 5 surrogate_model: gp pre_load_hooks: diff --git a/tests/test_yaml_run_args/run_args_invalid_key.yaml b/tests/test_yaml_run_args/run_args_invalid_key.yaml index 450dbd6d..f9095abd 100644 --- a/tests/test_yaml_run_args/run_args_invalid_key.yaml +++ b/tests/test_yaml_run_args/run_args_invalid_key.yaml @@ -24,8 +24,8 @@ error_handling: ignore_errors: True search: - searcher: "bayesian_optimization" - searcher_kwargs: + searcher: + strategy: "bayesian_optimization" initial_design_size: 5 surrogate_model: gp diff --git a/tests/test_yaml_run_args/run_args_invalid_type.yaml b/tests/test_yaml_run_args/run_args_invalid_type.yaml index 357f2a7b..065dd967 100644 --- a/tests/test_yaml_run_args/run_args_invalid_type.yaml +++ b/tests/test_yaml_run_args/run_args_invalid_type.yaml @@ -21,8 +21,8 @@ error_handling: ignore_errors: None search: - searcher: "bayesian_optimization" - searcher_kwargs: + searcher: + strategy: "bayesian_optimization" initial_design_size: 5 surrogate_model: gp diff --git a/tests/test_yaml_run_args/run_args_key_missing.yaml b/tests/test_yaml_run_args/run_args_key_missing.yaml index c36b0cbb..8394c5a0 100644 --- a/tests/test_yaml_run_args/run_args_key_missing.yaml +++ b/tests/test_yaml_run_args/run_args_key_missing.yaml @@ -14,8 +14,8 @@ continue_until_max_evaluation_completed: True loss_value_on_error: 2.4 cost_value_on_error: 2.1 ignore_errors: False -searcher: "bayesian_optimization" -searcher_kwargs: +searcher: + strategy: "bayesian_optimization" initial_design_size: 5 surrogate_model: gp pre_load_hooks: diff --git a/tests/test_yaml_run_args/run_args_optional_loading_format.yaml b/tests/test_yaml_run_args/run_args_optional_loading_format.yaml index e9acaa08..e4f2178a 100644 --- a/tests/test_yaml_run_args/run_args_optional_loading_format.yaml +++ b/tests/test_yaml_run_args/run_args_optional_loading_format.yaml @@ -19,7 +19,6 @@ ignore_errors: False searcher: # Optional Loading path: "neps/optimizers/bayesian_optimization/optimizer.py" name: BayesianOptimization -searcher_kwargs: initial_design_size: 5 surrogate_model: gp pre_load_hooks: diff --git a/tests/test_yaml_run_args/run_args_partial.yaml b/tests/test_yaml_run_args/run_args_partial.yaml index aa84460f..e07ed17a 100644 --- a/tests/test_yaml_run_args/run_args_partial.yaml +++ b/tests/test_yaml_run_args/run_args_partial.yaml @@ -19,8 +19,8 @@ parallelization_setup: continue_until_max_evaluation_completed: False search: - searcher: "bayesian_optimization" - searcher_kwargs: + searcher: + strategy: "bayesian_optimization" initial_design_size: 5 surrogate_model: gp diff --git a/tests/test_yaml_run_args/run_args_wrong_name.yaml b/tests/test_yaml_run_args/run_args_wrong_name.yaml index 1d90a140..ebfe640f 100644 --- a/tests/test_yaml_run_args/run_args_wrong_name.yaml +++ b/tests/test_yaml_run_args/run_args_wrong_name.yaml @@ -24,8 +24,8 @@ error_handling: ignore_errors: True search: - searcher: "bayesian_optimization" - searcher_kwargs: + searcher: + strategy: "bayesian_optimization" initial_design_size: 5 surrogate_model: gp diff --git a/tests/test_yaml_run_args/run_args_wrong_path.yaml b/tests/test_yaml_run_args/run_args_wrong_path.yaml index 0bd57a72..bc8feed6 100644 --- a/tests/test_yaml_run_args/run_args_wrong_path.yaml +++ b/tests/test_yaml_run_args/run_args_wrong_path.yaml @@ -24,8 +24,8 @@ error_handling: ignore_errors: True search: - searcher: "bayesian_optimization" - searcher_kwargs: + searcher: + strategy: "bayesian_optimization" initial_design_size: 5 surrogate_model: gp diff --git a/tests/test_yaml_run_args/test_declarative_usage_docs/customizing_neps_optimizer.yaml b/tests/test_yaml_run_args/test_declarative_usage_docs/customizing_neps_optimizer.yaml index 482e630c..34a9fe33 100644 --- a/tests/test_yaml_run_args/test_declarative_usage_docs/customizing_neps_optimizer.yaml +++ b/tests/test_yaml_run_args/test_declarative_usage_docs/customizing_neps_optimizer.yaml @@ -18,7 +18,7 @@ pipeline_space: root_directory: path/to/results/custominizing_neps_optimizer # Directory for result storage max_evaluations_total: 20 # Budget searcher: - algorithm: bayesian_optimization # name linked with neps keywords, more information click here..? + strategy: bayesian_optimization # name linked with neps keywords, more information click here..? # Specific arguments depending on the searcher initial_design_size: 7 surrogate_model: gp diff --git a/tests/test_yaml_run_args/test_declarative_usage_docs/loading_own_optimizer.yaml b/tests/test_yaml_run_args/test_declarative_usage_docs/loading_own_optimizer.yaml index 26897062..2e0e664b 100644 --- a/tests/test_yaml_run_args/test_declarative_usage_docs/loading_own_optimizer.yaml +++ b/tests/test_yaml_run_args/test_declarative_usage_docs/loading_own_optimizer.yaml @@ -1,6 +1,6 @@ run_pipeline: - path: path/to/your/run_pipeline.py # Path to the function file - name: example_pipeline # Function name within the file + path: tests/test_yaml_run_args/test_declarative_usage_docs/run_pipeline.py + name: run_pipeline pipeline_space: learning_rate: @@ -14,9 +14,11 @@ pipeline_space: root_directory: path/to/results # Directory for result storage max_evaluations_total: 20 # Budget searcher: - path: path/to/your/searcher.py # Path to the class - name: CustomOptimizer # class name within the file + path: "neps/optimizers/bayesian_optimization/optimizer.py" + name: BayesianOptimization # Specific arguments depending on your searcher initial_design_size: 7 surrogate_model: gp acquisition: EI + +overwrite_working_directory: True diff --git a/tests/test_yaml_run_args/test_declarative_usage_docs/set_up_optimizer.yaml b/tests/test_yaml_run_args/test_declarative_usage_docs/set_up_optimizer.yaml index 9d7e27f7..f65af743 100644 --- a/tests/test_yaml_run_args/test_declarative_usage_docs/set_up_optimizer.yaml +++ b/tests/test_yaml_run_args/test_declarative_usage_docs/set_up_optimizer.yaml @@ -1,4 +1,4 @@ -algorithm: bayesian_optimization +strategy: bayesian_optimization # Specific arguments depending on the searcher initial_design_size: 7 surrogate_model: gp diff --git a/tests/test_yaml_run_args/test_declarative_usage_docs/test_declarative_usage_docs.py b/tests/test_yaml_run_args/test_declarative_usage_docs/test_declarative_usage_docs.py index aa7ab8ee..c7f7a4be 100644 --- a/tests/test_yaml_run_args/test_declarative_usage_docs/test_declarative_usage_docs.py +++ b/tests/test_yaml_run_args/test_declarative_usage_docs/test_declarative_usage_docs.py @@ -10,7 +10,8 @@ "simple_example_including_run_pipeline.yaml", "full_configuration_template.yaml", "defining_hooks.yaml", - "customizing_neps_optimizer.yaml" + "customizing_neps_optimizer.yaml", + "loading_own_optimizer.yaml" ]) def test_run_with_yaml(yaml_file: str) -> None: """ diff --git a/tests/test_yaml_run_args/test_run_args_by_neps_run/config.yaml b/tests/test_yaml_run_args/test_run_args_by_neps_run/config.yaml index 2caf183e..fda6d4cd 100644 --- a/tests/test_yaml_run_args/test_run_args_by_neps_run/config.yaml +++ b/tests/test_yaml_run_args/test_run_args_by_neps_run/config.yaml @@ -17,10 +17,9 @@ parallelization_setup: max_evaluations_per_run: None continue_until_max_evaluation_completed: -search: - searcher: "bayesian_optimization" - searcher_kwargs: - initial_design_size: 5 - surrogate_model: gp +searcher: + strategy: "bayesian_optimization" + initial_design_size: 5 + surrogate_model: gp pre_load_hooks: None diff --git a/tests/test_yaml_run_args/test_run_args_by_neps_run/loading_pipeline_space.yaml b/tests/test_yaml_run_args/test_run_args_by_neps_run/loading_pipeline_space.yaml index 601a3c15..2a41902d 100644 --- a/tests/test_yaml_run_args/test_run_args_by_neps_run/loading_pipeline_space.yaml +++ b/tests/test_yaml_run_args/test_run_args_by_neps_run/loading_pipeline_space.yaml @@ -21,8 +21,8 @@ parallelization_setup: continue_until_max_evaluation_completed: search: - searcher: "bayesian_optimization" - searcher_kwargs: + searcher: + strategy: "bayesian_optimization" initial_design_size: 5 surrogate_model: gp diff --git a/tests/test_yaml_run_args/test_yaml_run_args.py b/tests/test_yaml_run_args/test_yaml_run_args.py index 71b236ce..ea93cd8b 100644 --- a/tests/test_yaml_run_args/test_yaml_run_args.py +++ b/tests/test_yaml_run_args/test_yaml_run_args.py @@ -1,6 +1,6 @@ import pytest import neps -from neps.utils.run_args_from_yaml import get_run_args_from_yaml +from neps.utils.run_args import get_run_args_from_yaml from neps.optimizers.bayesian_optimization.optimizer import BayesianOptimization from typing import Union, Callable, Dict, List, Type From 0d039a5c31dbad739d4395a0021756a484b0ef09 Mon Sep 17 00:00:00 2001 From: Daniel <63580393+danrgll@users.noreply.github.com> Date: Fri, 14 Jun 2024 10:16:04 +0200 Subject: [PATCH 15/29] change algorithm to strategy --- .../doc_yamls/customizing_neps_optimizer.yaml | 2 +- docs/doc_yamls/set_up_optimizer.yaml | 2 +- docs/reference/yaml_usage.md | 68 ------------------- 3 files changed, 2 insertions(+), 70 deletions(-) delete mode 100644 docs/reference/yaml_usage.md diff --git a/docs/doc_yamls/customizing_neps_optimizer.yaml b/docs/doc_yamls/customizing_neps_optimizer.yaml index b079193e..5237bc4d 100644 --- a/docs/doc_yamls/customizing_neps_optimizer.yaml +++ b/docs/doc_yamls/customizing_neps_optimizer.yaml @@ -14,7 +14,7 @@ pipeline_space: root_directory: path/to/results # Directory for result storage max_evaluations_total: 20 # Budget searcher: - algorithm: bayesian_optimization # name linked with neps keywords, more information click here..? + strategy: bayesian_optimization # name linked with neps keywords, more information click here..? # Specific arguments depending on the searcher initial_design_size: 7 surrogate_model: gp diff --git a/docs/doc_yamls/set_up_optimizer.yaml b/docs/doc_yamls/set_up_optimizer.yaml index 9d7e27f7..f65af743 100644 --- a/docs/doc_yamls/set_up_optimizer.yaml +++ b/docs/doc_yamls/set_up_optimizer.yaml @@ -1,4 +1,4 @@ -algorithm: bayesian_optimization +strategy: bayesian_optimization # Specific arguments depending on the searcher initial_design_size: 7 surrogate_model: gp diff --git a/docs/reference/yaml_usage.md b/docs/reference/yaml_usage.md deleted file mode 100644 index 6c664ca6..00000000 --- a/docs/reference/yaml_usage.md +++ /dev/null @@ -1,68 +0,0 @@ -!!! note "Work in Progress" - This document is currently a work in progress and may contain incomplete or preliminary information. - -# YAML Usage -For defining `run_args=`, some settings like `run_pipeline=`, `pre_load_hooks=`, and custom `searcher=` need both a path and a name to dynamically load them. -This is crucial for components where NePS must find and execute specific code. -Below, the configurations for dynamic loading are outlined: - -- `run_pipeline=`: - Specify the path and name of the pipeline function. This information allows NePS to find and initiate the desired - pipeline. - ```yaml - run_pipeline: - path: - name: - ``` - - -- **`pipeline_space`**: -The pipeline_space configuration supports two modes of definition: directly in a YAML file or through a Python -dictionary that NePS can dynamically load. - - **Direct YAML File Path**: Simply specify the path to your search space YAML. - ```yaml - pipeline_space: "path/to/your/search_space.yaml" - ``` - - **Python Dictionary**: To define pipeline_space as a Python dictionary, specify the file path containing the - dictionary and its name. This enables dynamic loading by NePS. - ```yaml - pipeline_space: - path: - name: - ``` - - -- **`searcher`**: -Configure the searcher to either utilize a built-in searcher or integrate a custom searcher of your creation. - - **Built-in Searcher**: For using an already integrated searcher, simply specify its key. This is the - straightforward method for standard optimization scenarios. - ```yaml - searcher: "bayesian_optimization" # key of the searcher - ``` - - **Custom Searcher**: If you've developed a custom optimization algorithm and wish to employ it within NePS, - specify the path and name of your custom searcher class. This allows NePS to dynamically load and use your - custom implementation. - ```yaml - searcher: - path: - name: - ``` - - -- **`pre_load_hooks`**: - Define hooks to be loaded before the main execution process begins. Assign each hook a unique identifier (e.g. - hook1, hook2..) and detail the path to its implementation file along with the function name. - This setup enables NePS to dynamically import and run these hooks during initialization. - ```yaml - pre_load_hooks: - hook1: # # Assign any unique key. - path: # Path to the hook's implementation file. - name: # Name of the hook. - hook2: - path: - name: - ``` - - -For detailed examples and specific use case configurations, visit [here](../examples/yaml_usage/index.md) - From 12bc54286caa1576206b5ad854867cdafea6d06f Mon Sep 17 00:00:00 2001 From: Daniel <63580393+danrgll@users.noreply.github.com> Date: Fri, 14 Jun 2024 12:31:04 +0200 Subject: [PATCH 16/29] update rm searcher_kwargs key from yaml for user --- neps_examples/{yaml_usage => declarative_usage}/README.md | 0 .../{yaml_usage => declarative_usage}/config.yaml | 8 ++------ .../{yaml_usage => declarative_usage}/hpo_example.py | 2 +- 3 files changed, 3 insertions(+), 7 deletions(-) rename neps_examples/{yaml_usage => declarative_usage}/README.md (100%) rename neps_examples/{yaml_usage => declarative_usage}/config.yaml (76%) rename neps_examples/{yaml_usage => declarative_usage}/hpo_example.py (98%) diff --git a/neps_examples/yaml_usage/README.md b/neps_examples/declarative_usage/README.md similarity index 100% rename from neps_examples/yaml_usage/README.md rename to neps_examples/declarative_usage/README.md diff --git a/neps_examples/yaml_usage/config.yaml b/neps_examples/declarative_usage/config.yaml similarity index 76% rename from neps_examples/yaml_usage/config.yaml rename to neps_examples/declarative_usage/config.yaml index c8d1addc..bf404a5a 100644 --- a/neps_examples/yaml_usage/config.yaml +++ b/neps_examples/declarative_usage/config.yaml @@ -1,14 +1,10 @@ experiment: - root_directory: "example_run" + root_directory: "results/example_run" max_evaluations_total: 20 overwrite_working_directory: True post_run_summary: True development_stage_id: "beta" -run_pipeline: - path: "hpo_example.py" - name: "training_pipeline" - pipeline_space: epochs: 5 learning_rate: @@ -19,7 +15,7 @@ pipeline_space: lower: 1 upper: 5 optimizer: - choices: [ "adam", "sgd" ] + choices: ["adam", "sgd"] num_neurons: lower: 64 upper: 128 diff --git a/neps_examples/yaml_usage/hpo_example.py b/neps_examples/declarative_usage/hpo_example.py similarity index 98% rename from neps_examples/yaml_usage/hpo_example.py rename to neps_examples/declarative_usage/hpo_example.py index 4865d273..8af8af2e 100644 --- a/neps_examples/yaml_usage/hpo_example.py +++ b/neps_examples/declarative_usage/hpo_example.py @@ -124,4 +124,4 @@ def training_pipeline(num_layers, num_neurons, epochs, learning_rate, optimizer) # Run optimization using neps.run(...). Arguments can be provided directly to neps.run # or defined in a configuration file (e.g., "config.yaml") passed through # the run_args parameter. - neps.run(run_args="config.yaml") + neps.run(training_pipeline, run_args="config.yaml") From 5f3e541bf1137277b8b197844263d78c1a86fba2 Mon Sep 17 00:00:00 2001 From: Daniel <63580393+danrgll@users.noreply.github.com> Date: Fri, 14 Jun 2024 12:34:45 +0200 Subject: [PATCH 17/29] update rm searcher_kwargs key from yaml for user --- neps/api.py | 13 ++--------- neps/utils/run_args.py | 22 ++++++++++++------- .../test_yaml_run_args/test_yaml_run_args.py | 19 +++++++--------- 3 files changed, 24 insertions(+), 30 deletions(-) diff --git a/neps/api.py b/neps/api.py index 82f2a3cd..e91d2ec2 100644 --- a/neps/api.py +++ b/neps/api.py @@ -283,7 +283,7 @@ def run( if inspect.isclass(searcher): if issubclass(searcher, BaseOptimizer): search_space = SearchSpace(**pipeline_space) - searcher = searcher(search_space) + searcher = searcher(search_space, **searcher_kwargs) else: # Raise an error if searcher is not a subclass of BaseOptimizer raise TypeError( @@ -410,7 +410,7 @@ def _run_args( raise TypeError(message) from e if isinstance(searcher, (str, Path)) and searcher not in \ - SearcherConfigs.get_searchers() and searcher is not "default": + SearcherConfigs.get_searchers() and searcher != "default": # The users has their own custom searcher. logging.info("Preparing to run user created searcher") @@ -450,15 +450,6 @@ def _run_args( # Map the old 'algorithm' argument to 'strategy' config['strategy'] = config.pop("algorithm") - # Check for deprecated 'algorithm' argument - if "algorithm" in config: - warnings.warn( - "The 'algorithm' argument is deprecated and will be removed in future versions. Please use 'strategy' instead.", - DeprecationWarning - ) - # Map the old 'algorithm' argument to 'strategy' - config['strategy'] = config.pop("algorithm") - if "strategy" in config: searcher_alg = config.pop("strategy") else: diff --git a/neps/utils/run_args.py b/neps/utils/run_args.py index c4f69ec0..f5b94566 100644 --- a/neps/utils/run_args.py +++ b/neps/utils/run_args.py @@ -61,7 +61,7 @@ def get_run_args_from_yaml(path: str) -> dict: settings = {} # List allowed NEPS run arguments with simple types (e.g., string, int). Parameters - # like 'searcher_kwargs', 'run_pipeline', 'preload_hooks', 'pipeline_space', + # like 'run_pipeline', 'preload_hooks', 'pipeline_space', # and 'searcher' are excluded due to needing specialized processing. expected_parameters = [ ROOT_DIRECTORY, @@ -147,7 +147,7 @@ def config_loader(path: str) -> dict: def extract_leaf_keys(d: dict, special_keys: dict | None = None) -> tuple[dict, dict]: """Recursive function to extract leaf keys and their values from a nested dictionary. - Special keys (e.g. 'searcher_kwargs', 'run_pipeline') are also extracted if present + Special keys (e.g.'run_pipeline') are also extracted if present and their corresponding values (dict) at any level in the nested structure. :param d: The dictionary to extract values from. @@ -157,7 +157,6 @@ def extract_leaf_keys(d: dict, special_keys: dict | None = None) -> tuple[dict, """ if special_keys is None: special_keys = { - SEARCHER_KWARGS: None, RUN_PIPELINE: None, PRE_LOAD_HOOKS: None, SEARCHER: None, @@ -183,8 +182,8 @@ def handle_special_argument_cases(settings: dict, special_configs: dict) -> None This function updates 'settings' with values from 'special_configs'. It handles specific keys that require more complex processing, such as 'pipeline_space' and 'searcher', which may need to load a function/dict from paths. It also manages nested - configurations like 'searcher_kwargs' and 'pre_load_hooks' which need individual - processing or function loading. + configurations like 'pre_load_hooks' which need individual processing or function + loading. Parameters: - settings (dict): The dictionary to be updated with processed configurations. @@ -269,8 +268,12 @@ def process_searcher(key: str, special_configs: dict, settings: dict): # determine if dict contains path_loading or the actual searcher config expected_keys = {"path", "name"} actual_keys = set(searcher.keys()) - if expected_keys == actual_keys: - searcher = load_and_return_object(searcher["path"], searcher["name"], key) + if expected_keys.issubset(actual_keys): + path = searcher.pop("path") + name = searcher.pop("name") + settings[SEARCHER_KWARGS] = searcher + searcher = load_and_return_object(path, name, key) + elif isinstance(searcher, (str, Path)): pass else: @@ -426,7 +429,7 @@ def check_run_args(settings: dict) -> None: if not all(callable(item) for item in value): raise TypeError("All items in 'pre_load_hooks' must be callable.") elif param == SEARCHER: - if not (isinstance(param, str) or issubclass(param, BaseOptimizer)): + if not (isinstance(param, (str, dict)) or issubclass(param, BaseOptimizer)): raise TypeError( "Parameter 'searcher' must be a string or a class that is a subclass " "of BaseOptimizer." @@ -510,6 +513,9 @@ def check_double_reference( if name == RUN_ARGS: # Ignoring run_args argument continue + if name == SEARCHER_KWARGS and func_arguments[name] == {}: + continue + if name in yaml_arguments: raise ValueError( f"Conflict for argument '{name}': Argument is defined both via " diff --git a/tests/test_yaml_run_args/test_yaml_run_args.py b/tests/test_yaml_run_args/test_yaml_run_args.py index ea93cd8b..99f6d070 100644 --- a/tests/test_yaml_run_args/test_yaml_run_args.py +++ b/tests/test_yaml_run_args/test_yaml_run_args.py @@ -111,8 +111,8 @@ def are_functions_equivalent(f1: Union[Callable, List[Callable]], "loss_value_on_error": 4.2, "cost_value_on_error": 3.7, "ignore_errors": True, - "searcher": "bayesian_optimization", - "searcher_kwargs": {"initial_design_size": 5, "surrogate_model": "gp"}, + "searcher": {"strategy": "bayesian_optimization", + "initial_design_size": 5, "surrogate_model": "gp"}, "pre_load_hooks": [hook1, hook2], }, ), @@ -133,8 +133,8 @@ def are_functions_equivalent(f1: Union[Callable, List[Callable]], "loss_value_on_error": 2.4, "cost_value_on_error": 2.1, "ignore_errors": False, - "searcher": "bayesian_optimization", - "searcher_kwargs": {"initial_design_size": 5, "surrogate_model": "gp"}, + "searcher": {"strategy": "bayesian_optimization", + "initial_design_size": 5, "surrogate_model": "gp"}, "pre_load_hooks": [hook1], }, ), @@ -147,8 +147,8 @@ def are_functions_equivalent(f1: Union[Callable, List[Callable]], "overwrite_working_directory": True, "post_run_summary": False, "continue_until_max_evaluation_completed": False, - "searcher": "bayesian_optimization", - "searcher_kwargs": {"initial_design_size": 5, "surrogate_model": "gp"}, + "searcher": {"strategy": "bayesian_optimization", + "initial_design_size": 5, "surrogate_model": "gp"}, }, ), ( @@ -178,11 +178,8 @@ def are_functions_equivalent(f1: Union[Callable, List[Callable]], "loss_value_on_error": 2.4, "cost_value_on_error": 2.1, "ignore_errors": False, - "searcher": BayesianOptimization, - "searcher_kwargs": { - "initial_design_size": 5, - "surrogate_model": "gp" - }, + "searcher": {"strategy": "bayesian_optimization", "initial_design_size": 5, + "surrogate_model": "gp"}, "pre_load_hooks": [hook1] }) From c06dde78b834d252714c15c9deb644d3a33837ee Mon Sep 17 00:00:00 2001 From: Daniel <63580393+danrgll@users.noreply.github.com> Date: Fri, 14 Jun 2024 12:52:24 +0200 Subject: [PATCH 18/29] fix pre-commit --- neps/utils/run_args.py | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/neps/utils/run_args.py b/neps/utils/run_args.py index f5b94566..0f5c005f 100644 --- a/neps/utils/run_args.py +++ b/neps/utils/run_args.py @@ -1,3 +1,7 @@ +"""This module provides utility functions for handling yaml content of run_args. +It includes functions for loading and processing configurations. +""" + from __future__ import annotations import importlib.util @@ -131,7 +135,7 @@ def config_loader(path: str) -> dict: KeyError: If 'run_args' is not the only top level key """ try: - with open(path) as file: + with open(path) as file: # noqa: PTH123 config = yaml.safe_load(file) except FileNotFoundError as e: raise FileNotFoundError( @@ -204,7 +208,7 @@ def handle_special_argument_cases(settings: dict, special_configs: dict) -> None settings[PRE_LOAD_HOOKS] = load_hooks_from_config(special_configs[PRE_LOAD_HOOKS]) -def process_pipeline_space(key: str, special_configs: dict, settings: dict): +def process_pipeline_space(key: str, special_configs: dict, settings: dict) -> None: """Process or load the pipeline space configuration. This function checks if the given key exists in the `special_configs` dictionary. @@ -248,7 +252,7 @@ def process_pipeline_space(key: str, special_configs: dict, settings: dict): settings[key] = processed_pipeline_space -def process_searcher(key: str, special_configs: dict, settings: dict): +def process_searcher(key: str, special_configs: dict, settings: dict) -> None: """Processes the searcher configuration and updates the settings dictionary. Checks if the key exists in special_configs. If found, it processes the @@ -284,7 +288,7 @@ def process_searcher(key: str, special_configs: dict, settings: dict): settings[key] = searcher -def process_run_pipeline(key: str, special_configs: dict, settings: dict): +def process_run_pipeline(key: str, special_configs: dict, settings: dict) -> None: """Processes the run pipeline configuration and updates the settings dictionary. Parameters: @@ -424,7 +428,7 @@ def check_run_args(settings: dict) -> None: if param in (DEVELOPMENT_STAGE_ID, TASK_ID): # this argument can be Any continue - elif param == PRE_LOAD_HOOKS: + elif param == PRE_LOAD_HOOKS: # noqa: RET507 # check if all items in pre_load_hooks are callable objects if not all(callable(item) for item in value): raise TypeError("All items in 'pre_load_hooks' must be callable.") From ff566dd17d7415a392bf1ae4fa70cd823db24893 Mon Sep 17 00:00:00 2001 From: Daniel <63580393+danrgll@users.noreply.github.com> Date: Mon, 17 Jun 2024 11:32:40 +0200 Subject: [PATCH 19/29] add providing arguments for loaded class BaseOptimizer via yaml + fix pre-commit related errors --- neps/api.py | 10 ++++++---- neps/utils/run_args.py | 16 ++++++++-------- tests/test_yaml_run_args/test_yaml_run_args.py | 13 +++++++------ 3 files changed, 21 insertions(+), 18 deletions(-) diff --git a/neps/api.py b/neps/api.py index e91d2ec2..3f35bd88 100644 --- a/neps/api.py +++ b/neps/api.py @@ -224,7 +224,6 @@ def run( if run_args: optim_settings = get_run_args_from_yaml(run_args) check_double_reference(run, locals(), optim_settings) - run_pipeline = optim_settings.get("run_pipeline", run_pipeline) root_directory = optim_settings.get("root_directory", root_directory) pipeline_space = optim_settings.get("pipeline_space", pipeline_space) @@ -250,8 +249,8 @@ def run( cost_value_on_error) pre_load_hooks = optim_settings.get("pre_load_hooks", pre_load_hooks) searcher = optim_settings.get("searcher", searcher) - for key, value in optim_settings.get("searcher_kwargs", searcher_kwargs).items(): - searcher_kwargs[key] = value + # considers arguments of a provided SubClass of BaseOptimizer + searcher_class_arguments = optim_settings.get("custom_class_searcher_kwargs", {}) # check if necessary arguments are provided. check_essential_arguments( @@ -283,7 +282,10 @@ def run( if inspect.isclass(searcher): if issubclass(searcher, BaseOptimizer): search_space = SearchSpace(**pipeline_space) - searcher = searcher(search_space, **searcher_kwargs) + # aligns with the behavior of the internal neps searcher which also overwrites + # its arguments by using searcher_kwargs + merge_kwargs = {**searcher_class_arguments, **searcher_kwargs} + searcher = searcher(search_space, **merge_kwargs) else: # Raise an error if searcher is not a subclass of BaseOptimizer raise TypeError( diff --git a/neps/utils/run_args.py b/neps/utils/run_args.py index 0f5c005f..46c12414 100644 --- a/neps/utils/run_args.py +++ b/neps/utils/run_args.py @@ -35,7 +35,9 @@ IGNORE_ERROR = "ignore_errors" SEARCHER = "searcher" PRE_LOAD_HOOKS = "pre_load_hooks" -SEARCHER_KWARGS = "searcher_kwargs" +# searcher_kwargs is used differently in yaml and just play a role for considering +# arguments of a custom searcher class (BaseOptimizer) +SEARCHER_KWARGS = "custom_class_searcher_kwargs" MAX_EVALUATIONS_PER_RUN = "max_evaluations_per_run" @@ -229,6 +231,7 @@ def process_pipeline_space(key: str, special_configs: dict, settings: dict) -> N """ if special_configs.get(key) is not None: pipeline_space = special_configs[key] + # Define the type of processed_pipeline_space to accommodate both situations if isinstance(pipeline_space, dict): # determine if dict contains path_loading or the actual search space expected_keys = {"path", "name"} @@ -240,7 +243,7 @@ def process_pipeline_space(key: str, special_configs: dict, settings: dict) -> N # pipeline_space stored in a python dict, not using a yaml processed_pipeline_space = load_and_return_object( pipeline_space["path"], pipeline_space["name"], key - ) + ) # type: ignore elif isinstance(pipeline_space, str): # load yaml from path processed_pipeline_space = pipeline_space_from_yaml(pipeline_space) @@ -333,7 +336,7 @@ def load_and_return_object(module_path: str, object_name: str, key: str) -> obje the issue. """ - def import_object(path): + def import_object(path: str) -> object | None: try: # Convert file system path to module path, removing '.py' if present. module_name = ( @@ -433,7 +436,7 @@ def check_run_args(settings: dict) -> None: if not all(callable(item) for item in value): raise TypeError("All items in 'pre_load_hooks' must be callable.") elif param == SEARCHER: - if not (isinstance(param, (str, dict)) or issubclass(param, BaseOptimizer)): + if not (isinstance(value, (str, dict)) or issubclass(value, BaseOptimizer)): raise TypeError( "Parameter 'searcher' must be a string or a class that is a subclass " "of BaseOptimizer." @@ -443,7 +446,7 @@ def check_run_args(settings: dict) -> None: expected_type = expected_types[param] except KeyError as e: raise KeyError(f"{param} is not a valid argument of neps") from e - if not isinstance(value, expected_type): + if not isinstance(value, expected_type): # type: ignore raise TypeError( f"Parameter '{param}' expects a value of type {expected_type}, got " f"{type(value)} instead." @@ -517,9 +520,6 @@ def check_double_reference( if name == RUN_ARGS: # Ignoring run_args argument continue - if name == SEARCHER_KWARGS and func_arguments[name] == {}: - continue - if name in yaml_arguments: raise ValueError( f"Conflict for argument '{name}': Argument is defined both via " diff --git a/tests/test_yaml_run_args/test_yaml_run_args.py b/tests/test_yaml_run_args/test_yaml_run_args.py index 99f6d070..3ce2185f 100644 --- a/tests/test_yaml_run_args/test_yaml_run_args.py +++ b/tests/test_yaml_run_args/test_yaml_run_args.py @@ -17,14 +17,14 @@ def run_pipeline(): return -def hook1(): +def hook1(sampler): """func to test loading of pre_load_hooks""" - return + return sampler -def hook2(): +def hook2(sampler): """func to test loading of pre_load_hooks""" - return + return sampler def check_run_args(yaml_path_run_args: str, expected_output: Dict) -> None: @@ -178,8 +178,9 @@ def are_functions_equivalent(f1: Union[Callable, List[Callable]], "loss_value_on_error": 2.4, "cost_value_on_error": 2.1, "ignore_errors": False, - "searcher": {"strategy": "bayesian_optimization", "initial_design_size": 5, - "surrogate_model": "gp"}, + "searcher": BayesianOptimization, + "custom_class_searcher_kwargs": {'initial_design_size': 5, + 'surrogate_model': 'gp'}, "pre_load_hooks": [hook1] }) From fbc3d892b1594392ef39663d6e3b76af3e4143df Mon Sep 17 00:00:00 2001 From: Daniel <63580393+danrgll@users.noreply.github.com> Date: Mon, 17 Jun 2024 12:55:33 +0200 Subject: [PATCH 20/29] add searcher_args to searcher_info for custom class optimizer loaded via yaml --- neps/api.py | 1 + 1 file changed, 1 insertion(+) diff --git a/neps/api.py b/neps/api.py index 3f35bd88..b909d4d3 100644 --- a/neps/api.py +++ b/neps/api.py @@ -285,6 +285,7 @@ def run( # aligns with the behavior of the internal neps searcher which also overwrites # its arguments by using searcher_kwargs merge_kwargs = {**searcher_class_arguments, **searcher_kwargs} + searcher_info["searcher_args"] = merge_kwargs searcher = searcher(search_space, **merge_kwargs) else: # Raise an error if searcher is not a subclass of BaseOptimizer From 659614f79fdfc426aebacbbc926ffb2d029f66b4 Mon Sep 17 00:00:00 2001 From: Daniel <63580393+danrgll@users.noreply.github.com> Date: Mon, 17 Jun 2024 13:37:57 +0200 Subject: [PATCH 21/29] add tests + fix docs --- docs/doc_yamls/pipeline_space.yaml | 41 +++++++++---------- .../loading_pipeline_space_dict.yaml | 8 ++-- .../outsourcing_optimizer.yaml | 8 ++-- .../outsourcing_pipeline_space.yaml | 8 ++-- .../pipeline_space.py | 8 ++++ .../pipeline_space.yaml | 37 ++++++++--------- .../test_declarative_usage_docs.py | 5 ++- 7 files changed, 60 insertions(+), 55 deletions(-) create mode 100644 tests/test_yaml_run_args/test_declarative_usage_docs/pipeline_space.py diff --git a/docs/doc_yamls/pipeline_space.yaml b/docs/doc_yamls/pipeline_space.yaml index a4782db5..7c03bd3f 100644 --- a/docs/doc_yamls/pipeline_space.yaml +++ b/docs/doc_yamls/pipeline_space.yaml @@ -1,22 +1,21 @@ # pipeline_space including priors and fidelity -pipeline_space: - learning_rate: - lower: 1e-5 - upper: 1e-1 - log: True # Log scale for learning rate - default: 1e-2 - default_confidence: "medium" - epochs: - lower: 5 - upper: 20 - is_fidelity: True - dropout_rate: - lower: 0.1 - upper: 0.5 - default: 0.2 - default_confidence: "high" - optimizer: - choices: [adam, sgd, adamw] - default: adam - # default confidence low - batch_size: 64 +learning_rate: + lower: 1e-5 + upper: 1e-1 + log: True # Log scale for learning rate + default: 1e-2 + default_confidence: "medium" +epochs: + lower: 5 + upper: 20 + is_fidelity: True +dropout_rate: + lower: 0.1 + upper: 0.5 + default: 0.2 + default_confidence: "high" +optimizer: + choices: [adam, sgd, adamw] + default: adam + # if default confidence is not defined it gets its default 'low' +batch_size: 64 diff --git a/tests/test_yaml_run_args/test_declarative_usage_docs/loading_pipeline_space_dict.yaml b/tests/test_yaml_run_args/test_declarative_usage_docs/loading_pipeline_space_dict.yaml index a8185c79..64366e3a 100644 --- a/tests/test_yaml_run_args/test_declarative_usage_docs/loading_pipeline_space_dict.yaml +++ b/tests/test_yaml_run_args/test_declarative_usage_docs/loading_pipeline_space_dict.yaml @@ -1,11 +1,11 @@ # Loading pipeline space from a python dict run_pipeline: - path: path/to/your/run_pipeline.py # Path to the function file - name: example_pipeline # Function name within the file + path: tests/test_yaml_run_args/test_declarative_usage_docs/run_pipeline.py + name: run_pipeline_constant pipeline_space: - path: path/to/your/search_space.py # Path to the dict file + path: tests/test_yaml_run_args/test_declarative_usage_docs/pipeline_space.py name: pipeline_space # Name of the dict instance -root_directory: path/to/results # Directory for result storage +root_directory: path/to/results_loading_pipeline_space # Directory for result storage max_evaluations_total: 20 # Budget diff --git a/tests/test_yaml_run_args/test_declarative_usage_docs/outsourcing_optimizer.yaml b/tests/test_yaml_run_args/test_declarative_usage_docs/outsourcing_optimizer.yaml index e6a3cea5..c905f55b 100644 --- a/tests/test_yaml_run_args/test_declarative_usage_docs/outsourcing_optimizer.yaml +++ b/tests/test_yaml_run_args/test_declarative_usage_docs/outsourcing_optimizer.yaml @@ -1,7 +1,7 @@ # Optimizer settings from YAML configuration run_pipeline: - path: path/to/your/run_pipeline.py # Path to the function file - name: example_pipeline # Function name within the file + path: tests/test_yaml_run_args/test_declarative_usage_docs/run_pipeline.py + name: run_pipeline pipeline_space: learning_rate: @@ -12,7 +12,7 @@ pipeline_space: choices: [adam, sgd, adamw] epochs: 50 -root_directory: path/to/results # Directory for result storage +root_directory: path/to/results_outsourcing_optimizer # Directory for result storage max_evaluations_total: 20 # Budget -searcher: path/to/your/searcher_setup.yaml +searcher: tests/test_yaml_run_args/test_declarative_usage_docs/set_up_optimizer.yaml diff --git a/tests/test_yaml_run_args/test_declarative_usage_docs/outsourcing_pipeline_space.yaml b/tests/test_yaml_run_args/test_declarative_usage_docs/outsourcing_pipeline_space.yaml index 68558569..f28781ec 100644 --- a/tests/test_yaml_run_args/test_declarative_usage_docs/outsourcing_pipeline_space.yaml +++ b/tests/test_yaml_run_args/test_declarative_usage_docs/outsourcing_pipeline_space.yaml @@ -1,10 +1,10 @@ # Pipeline space settings from YAML run_pipeline: - path: path/to/your/run_pipeline.py # Path to the function file - name: example_pipeline # Function name within the file + path: tests/test_yaml_run_args/test_declarative_usage_docs/run_pipeline.py + name: run_pipeline_constant -pipeline_space: path/to/your/pipeline_space.yaml +pipeline_space: tests/test_yaml_run_args/test_declarative_usage_docs/pipeline_space.yaml -root_directory: path/to/results # Directory for result storage +root_directory: path/to/results_outsourcing_pipeline_space # Directory for result storage max_evaluations_total: 20 # Budget diff --git a/tests/test_yaml_run_args/test_declarative_usage_docs/pipeline_space.py b/tests/test_yaml_run_args/test_declarative_usage_docs/pipeline_space.py new file mode 100644 index 00000000..79a034db --- /dev/null +++ b/tests/test_yaml_run_args/test_declarative_usage_docs/pipeline_space.py @@ -0,0 +1,8 @@ +import neps + +pipeline_space = dict( + learning_rate=neps.FloatParameter(lower=1e-5, upper=1e-1, log=True), + epochs=neps.IntegerParameter(lower=5, upper=20, is_fidelity=True), + optimizer=neps.CategoricalParameter(choices=["adam", "sgd", "adamw"]), + batch_size=neps.ConstantParameter(value=64) +) diff --git a/tests/test_yaml_run_args/test_declarative_usage_docs/pipeline_space.yaml b/tests/test_yaml_run_args/test_declarative_usage_docs/pipeline_space.yaml index a4782db5..f8c7a2aa 100644 --- a/tests/test_yaml_run_args/test_declarative_usage_docs/pipeline_space.yaml +++ b/tests/test_yaml_run_args/test_declarative_usage_docs/pipeline_space.yaml @@ -1,22 +1,17 @@ # pipeline_space including priors and fidelity -pipeline_space: - learning_rate: - lower: 1e-5 - upper: 1e-1 - log: True # Log scale for learning rate - default: 1e-2 - default_confidence: "medium" - epochs: - lower: 5 - upper: 20 - is_fidelity: True - dropout_rate: - lower: 0.1 - upper: 0.5 - default: 0.2 - default_confidence: "high" - optimizer: - choices: [adam, sgd, adamw] - default: adam - # default confidence low - batch_size: 64 +learning_rate: + lower: 1e-5 + upper: 1e-1 + log: True # Log scale for learning rate + default: 1e-2 + default_confidence: "medium" +epochs: + lower: 5 + upper: 20 + default: 10 + is_fidelity: True +optimizer: + choices: [adam, sgd, adamw] + default: adam + default_confidence: low +batch_size: 64 diff --git a/tests/test_yaml_run_args/test_declarative_usage_docs/test_declarative_usage_docs.py b/tests/test_yaml_run_args/test_declarative_usage_docs/test_declarative_usage_docs.py index c7f7a4be..5d6d8368 100644 --- a/tests/test_yaml_run_args/test_declarative_usage_docs/test_declarative_usage_docs.py +++ b/tests/test_yaml_run_args/test_declarative_usage_docs/test_declarative_usage_docs.py @@ -11,7 +11,10 @@ "full_configuration_template.yaml", "defining_hooks.yaml", "customizing_neps_optimizer.yaml", - "loading_own_optimizer.yaml" + "loading_own_optimizer.yaml", + "loading_pipeline_space_dict.yaml", + "outsourcing_optimizer.yaml", + "outsourcing_pipeline_space.yaml" ]) def test_run_with_yaml(yaml_file: str) -> None: """ From f23c88281211bc346d00444b18ac8e659ce25cb0 Mon Sep 17 00:00:00 2001 From: Daniel <63580393+danrgll@users.noreply.github.com> Date: Mon, 17 Jun 2024 18:58:25 +0200 Subject: [PATCH 22/29] update docs --- .../doc_yamls/customizing_neps_optimizer.yaml | 6 ++-- docs/doc_yamls/defining_hooks.yaml | 6 ++-- .../full_configuration_template.yaml | 2 +- docs/doc_yamls/loading_own_optimizer.yaml | 7 ++-- .../loading_pipeline_space_dict.yaml | 6 ++-- docs/doc_yamls/outsourcing_optimizer.yaml | 2 +- .../doc_yamls/outsourcing_pipeline_space.yaml | 4 +-- docs/doc_yamls/pipeline_space.yaml | 2 +- ...simple_example_including_run_pipeline.yaml | 2 +- docs/reference/declarative_usage.md | 15 ++++---- docs/reference/neps_run.md | 36 +++++++------------ docs/reference/optimizers.md | 4 +-- docs/reference/pipeline_space.md | 3 -- .../customizing_neps_optimizer.yaml | 3 +- 14 files changed, 45 insertions(+), 53 deletions(-) diff --git a/docs/doc_yamls/customizing_neps_optimizer.yaml b/docs/doc_yamls/customizing_neps_optimizer.yaml index 5237bc4d..a047be6d 100644 --- a/docs/doc_yamls/customizing_neps_optimizer.yaml +++ b/docs/doc_yamls/customizing_neps_optimizer.yaml @@ -1,3 +1,4 @@ +# Customizing NePS Searcher run_pipeline: path: path/to/your/run_pipeline.py # Path to the function file name: example_pipeline # Function name within the file @@ -12,9 +13,10 @@ pipeline_space: epochs: 50 root_directory: path/to/results # Directory for result storage -max_evaluations_total: 20 # Budget +max_evaluations_total: 20 # Budget searcher: - strategy: bayesian_optimization # name linked with neps keywords, more information click here..? + strategy: bayesian_optimization # key for neps searcher + name: "my_bayesian" # optional; changing the searcher_name for better recognition # Specific arguments depending on the searcher initial_design_size: 7 surrogate_model: gp diff --git a/docs/doc_yamls/defining_hooks.yaml b/docs/doc_yamls/defining_hooks.yaml index 186acaab..640d2004 100644 --- a/docs/doc_yamls/defining_hooks.yaml +++ b/docs/doc_yamls/defining_hooks.yaml @@ -1,4 +1,4 @@ -# Basic NEPS Configuration Example +# Hooks run_pipeline: path: path/to/your/run_pipeline.py # Path to the function file name: example_pipeline # Function name within the file @@ -17,8 +17,8 @@ pipeline_space: batch_size: 64 root_directory: path/to/results # Directory for result storage -max_evaluations_total: 20 # Budget +max_evaluations_total: 20 # Budget pre_load_hooks: hook1: path/to/your/hooks.py # (function_name: Path to the function's file) - hook2: path/to/your/hooks.py # Different function name from the same file source + hook2: path/to/your/hooks.py # Different function name 'hook2' from the same file source diff --git a/docs/doc_yamls/full_configuration_template.yaml b/docs/doc_yamls/full_configuration_template.yaml index ca6abf1a..84fb7523 100644 --- a/docs/doc_yamls/full_configuration_template.yaml +++ b/docs/doc_yamls/full_configuration_template.yaml @@ -7,7 +7,7 @@ pipeline_space: learning_rate: lower: 1e-5 upper: 1e-1 - log: True # Log scale for learning rate + log: True epochs: lower: 5 upper: 20 diff --git a/docs/doc_yamls/loading_own_optimizer.yaml b/docs/doc_yamls/loading_own_optimizer.yaml index 26897062..3058f936 100644 --- a/docs/doc_yamls/loading_own_optimizer.yaml +++ b/docs/doc_yamls/loading_own_optimizer.yaml @@ -1,3 +1,4 @@ +# Loading Optimizer Class run_pipeline: path: path/to/your/run_pipeline.py # Path to the function file name: example_pipeline # Function name within the file @@ -12,10 +13,10 @@ pipeline_space: epochs: 50 root_directory: path/to/results # Directory for result storage -max_evaluations_total: 20 # Budget +max_evaluations_total: 20 # Budget searcher: - path: path/to/your/searcher.py # Path to the class - name: CustomOptimizer # class name within the file + path: path/to/your/searcher.py # Path to the class + name: CustomOptimizer # class name within the file # Specific arguments depending on your searcher initial_design_size: 7 surrogate_model: gp diff --git a/docs/doc_yamls/loading_pipeline_space_dict.yaml b/docs/doc_yamls/loading_pipeline_space_dict.yaml index a8185c79..96643865 100644 --- a/docs/doc_yamls/loading_pipeline_space_dict.yaml +++ b/docs/doc_yamls/loading_pipeline_space_dict.yaml @@ -1,11 +1,11 @@ # Loading pipeline space from a python dict run_pipeline: path: path/to/your/run_pipeline.py # Path to the function file - name: example_pipeline # Function name within the file + name: example_pipeline # Function name within the file pipeline_space: path: path/to/your/search_space.py # Path to the dict file - name: pipeline_space # Name of the dict instance + name: pipeline_space # Name of the dict instance root_directory: path/to/results # Directory for result storage -max_evaluations_total: 20 # Budget +max_evaluations_total: 20 # Budget diff --git a/docs/doc_yamls/outsourcing_optimizer.yaml b/docs/doc_yamls/outsourcing_optimizer.yaml index e6a3cea5..b0fe734b 100644 --- a/docs/doc_yamls/outsourcing_optimizer.yaml +++ b/docs/doc_yamls/outsourcing_optimizer.yaml @@ -13,6 +13,6 @@ pipeline_space: epochs: 50 root_directory: path/to/results # Directory for result storage -max_evaluations_total: 20 # Budget +max_evaluations_total: 20 # Budget searcher: path/to/your/searcher_setup.yaml diff --git a/docs/doc_yamls/outsourcing_pipeline_space.yaml b/docs/doc_yamls/outsourcing_pipeline_space.yaml index 68558569..c3d8eaed 100644 --- a/docs/doc_yamls/outsourcing_pipeline_space.yaml +++ b/docs/doc_yamls/outsourcing_pipeline_space.yaml @@ -1,4 +1,4 @@ -# Pipeline space settings from YAML +# Pipeline space settings from separate YAML run_pipeline: path: path/to/your/run_pipeline.py # Path to the function file name: example_pipeline # Function name within the file @@ -6,5 +6,5 @@ run_pipeline: pipeline_space: path/to/your/pipeline_space.yaml root_directory: path/to/results # Directory for result storage -max_evaluations_total: 20 # Budget +max_evaluations_total: 20 # Budget diff --git a/docs/doc_yamls/pipeline_space.yaml b/docs/doc_yamls/pipeline_space.yaml index 7c03bd3f..bae58406 100644 --- a/docs/doc_yamls/pipeline_space.yaml +++ b/docs/doc_yamls/pipeline_space.yaml @@ -1,4 +1,4 @@ -# pipeline_space including priors and fidelity +# Pipeline_space including priors and fidelity learning_rate: lower: 1e-5 upper: 1e-1 diff --git a/docs/doc_yamls/simple_example_including_run_pipeline.yaml b/docs/doc_yamls/simple_example_including_run_pipeline.yaml index 52c52e19..9c40bfad 100644 --- a/docs/doc_yamls/simple_example_including_run_pipeline.yaml +++ b/docs/doc_yamls/simple_example_including_run_pipeline.yaml @@ -1,7 +1,7 @@ # Simple NePS configuration including run_pipeline run_pipeline: path: path/to/your/run_pipeline.py # Path to the function file - name: example_pipeline # Function name within the file + name: example_pipeline # Function name within the file pipeline_space: learning_rate: diff --git a/docs/reference/declarative_usage.md b/docs/reference/declarative_usage.md index d9ea503c..bf175bab 100644 --- a/docs/reference/declarative_usage.md +++ b/docs/reference/declarative_usage.md @@ -1,10 +1,8 @@ -!!! note "Work in Progress" - This document is currently a work in progress and may contain incomplete or preliminary information. ## Introduction ### Configuring with YAML Configure your experiments using a YAML file, which serves as a central reference for setting up your project. -This approach simplifies sharing, reproducing, and modifying configurations. +This approach simplifies sharing, reproducing and modifying configurations. #### Simple YAML Example Below is a straightforward YAML configuration example for NePS covering the required arguments. === "config.yaml" @@ -27,7 +25,7 @@ Below is a straightforward YAML configuration example for NePS covering the requ ``` -#### Including `run_pipeline` in config.yaml for External Referencing +#### Including `run_pipeline` in `run_args` for External Referencing In addition to setting experimental parameters via YAML, this configuration example also specifies the pipeline function and its location, enabling more flexible project structures. === "config.yaml" @@ -65,7 +63,7 @@ but also advanced settings for more complex setups. The `searcher` key used in the YAML configuration corresponds to the same keys used for selecting an optimizer directly through `neps.run`. For a detailed list of integrated optimizers, see [here](optimizers.md#list-available-searchers) -!!! note "Note on Undefined Keys" +!!! note "Note on undefined keys in `run_args` (config.yaml)" Not all configurations are explicitly defined in this template. Any undefined key in the YAML file is mapped to the internal default settings of NePS. This ensures that your experiments can run even if certain parameters are omitted. @@ -158,7 +156,10 @@ search spaces must be loaded via a dictionary, which is then referenced in the ` ### Integrating Custom Optimizers -You can also load your own custom optimizer and change its arguments in `config.yaml`. +For people who want to write their own optimizer class as a subclass of the base optimizer, you can load your own +custom optimizer class and define its arguments in `config.yaml`. + +Note: You can still overwrite arguments via searcher_kwargs of `neps.run` like for the internal searchers. === "config.yaml" ```yaml --8<-- "docs/doc_yamls/loading_own_optimizer.yaml" @@ -173,6 +174,8 @@ You can also load your own custom optimizer and change its arguments in `config. neps.run(run_args="path/to/your/config.yaml") ``` + + ### Adding Custom Hooks to Your Configuration Define hooks in your YAML configuration to extend the functionality of your experiment. === "config.yaml" diff --git a/docs/reference/neps_run.md b/docs/reference/neps_run.md index 84392829..1d9f3c88 100644 --- a/docs/reference/neps_run.md +++ b/docs/reference/neps_run.md @@ -190,11 +190,7 @@ Any new workers that come online will automatically pick up work and work togeth ## YAML Configuration You have the option to configure all arguments using a YAML file through [`neps.run(run_args=...)`][neps.api.run]. -For more on yaml usage, please visit the dedicated [page on usage of YAML with NePS](../reference/yaml_usage.md). - -!!! example "In Progress" - - This feature is currently in development and is subject to change. +For more on yaml usage, please visit the dedicated [page on usage of YAML with NePS](../reference/declarative_usage.md). Parameters not explicitly defined within this file will receive their default values. @@ -203,18 +199,17 @@ Parameters not explicitly defined within this file will receive their default va ```yaml # path/to/your/config.yaml - run_args: - run_pipeline: - path: "path/to/your/run_pipeline.py" # File path of the run_pipeline function - name: "name_of_your_run_pipeline" # Function name - pipeline_space: "path/to/your/search_space.yaml" # Path of the search space yaml file - root_directory: "neps_results" # Output directory for results - max_evaluations_total: 100 - post_run_summary: # Defaults applied if left empty - searcher: "bayesian_optimization" - searcher_kwargs: - initial_design_size: 5 - surrogate_model: "gp" + run_pipeline: + path: "path/to/your/run_pipeline.py" # File path of the run_pipeline function + name: "name_of_your_run_pipeline" # Function name + pipeline_space: "path/to/your/search_space.yaml" # Path of the search space yaml file + root_directory: "neps_results" # Output directory for results + max_evaluations_total: 100 + post_run_summary: # Defaults applied if left empty + searcher: + strategy: "bayesian_optimization" + initial_design_size: 5 + surrogate_model: "gp" ``` === "Python" @@ -223,13 +218,6 @@ Parameters not explicitly defined within this file will receive their default va neps.run(run_args="path/to/your/config.yaml") ``` -!!! warning - - Currently we have a strict usage for `run_args`. - So you can define either all arguments by providing them directly to neps.run or via the yaml file. - This might change in the future. - If you use yaml, directly provided arguments get overwritten either by the defined yaml config or the default value. - ## Handling Errors Things go wrong during optimization runs and it's important to consider what to do in these cases. By default, NePS will halt the optimization process when an error but you can choose to `ignore_errors=`, providing a `loss_value_on_error=` and `cost_value_on_error=` to control what values should be reported to the optimization process. diff --git a/docs/reference/optimizers.md b/docs/reference/optimizers.md index cc779391..c85f73cf 100644 --- a/docs/reference/optimizers.md +++ b/docs/reference/optimizers.md @@ -59,8 +59,8 @@ The library will then load your custom settings and use them for optimization. Here's the format of a custom YAML (`custom_bo.yaml`) configuration using `Bayesian Optimization` as an example: ```yaml -algorithm: bayesian_optimization -name: my_custom_bo # # optional; otherwise, your searcher will be named after your YAML file, here 'custom_bo'. +strategy: bayesian_optimization +name: my_custom_bo # optional; otherwise, your searcher will be named after your YAML file, here 'custom_bo'. # Specific arguments depending on the searcher initial_design_size: 7 surrogate_model: gp diff --git a/docs/reference/pipeline_space.md b/docs/reference/pipeline_space.md index 796cd1e1..172f05dd 100644 --- a/docs/reference/pipeline_space.md +++ b/docs/reference/pipeline_space.md @@ -128,9 +128,6 @@ the NePS will automatically infer the data type based on the value provided. If none of these hold, an error will be raised. -Details on how YAML interpret boolean values can be found later on, -[here](#important-note-on-yaml-data-type-interpretation) - ## Using ConfigSpace diff --git a/tests/test_yaml_run_args/test_declarative_usage_docs/customizing_neps_optimizer.yaml b/tests/test_yaml_run_args/test_declarative_usage_docs/customizing_neps_optimizer.yaml index 34a9fe33..d31d1f3f 100644 --- a/tests/test_yaml_run_args/test_declarative_usage_docs/customizing_neps_optimizer.yaml +++ b/tests/test_yaml_run_args/test_declarative_usage_docs/customizing_neps_optimizer.yaml @@ -18,7 +18,8 @@ pipeline_space: root_directory: path/to/results/custominizing_neps_optimizer # Directory for result storage max_evaluations_total: 20 # Budget searcher: - strategy: bayesian_optimization # name linked with neps keywords, more information click here..? + strategy: bayesian_optimization + name: "my_bayesian" # Specific arguments depending on the searcher initial_design_size: 7 surrogate_model: gp From 736b58a4c58c86a91f2e41ddc2d5ac73a25cbe24 Mon Sep 17 00:00:00 2001 From: Daniel <63580393+danrgll@users.noreply.github.com> Date: Mon, 17 Jun 2024 19:09:09 +0200 Subject: [PATCH 23/29] adapt SearcherConfigs to the new dict design of optimizers --- neps/optimizers/info.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/neps/optimizers/info.py b/neps/optimizers/info.py index 2ed03f30..7088f341 100644 --- a/neps/optimizers/info.py +++ b/neps/optimizers/info.py @@ -56,7 +56,7 @@ def get_available_algorithms() -> list[str]: file_path = os.path.join(folder_path, filename) with open(file_path) as file: searcher_config = yaml.safe_load(file) - algorithm = searcher_config["searcher_init"].get("algorithm") + algorithm = searcher_config.get("strategy") if algorithm: prev_algorithms.add(algorithm) @@ -81,7 +81,7 @@ def get_searcher_from_algorithm(algorithm: str) -> list[str]: file_path = os.path.join(folder_path, filename) with open(file_path) as file: searcher_config = yaml.safe_load(file) - if searcher_config["searcher_init"].get("algorithm") == algorithm: + if searcher_config.get("strategy") == algorithm: searchers.append(os.path.splitext(filename)[0]) return searchers From 285b9939d2be99245b315d34ae5703369fceff07 Mon Sep 17 00:00:00 2001 From: Daniel <63580393+danrgll@users.noreply.github.com> Date: Tue, 18 Jun 2024 09:59:53 +0200 Subject: [PATCH 24/29] clean up code and docsctrings --- neps/utils/run_args.py | 81 +++++++++++++++++++----------------------- 1 file changed, 37 insertions(+), 44 deletions(-) diff --git a/neps/utils/run_args.py b/neps/utils/run_args.py index 46c12414..71870c98 100644 --- a/neps/utils/run_args.py +++ b/neps/utils/run_args.py @@ -9,7 +9,7 @@ import logging import sys from pathlib import Path -from typing import Any, Callable +from typing import Callable import yaml @@ -45,13 +45,13 @@ def get_run_args_from_yaml(path: str) -> dict: """Load and validate NEPS run arguments from a specified YAML configuration file provided via run_args. - This function reads a YAML file, extracts the arguments required by NEPS, + This function reads a YAML file, extracts the arguments required by NePS, validates these arguments, and then returns them in a dictionary. It checks for the presence and validity of expected parameters, and distinctively handles more complex configurations, specifically those that are dictionaries(e.g. pipeline_space) or objects(e.g. run_pipeline) requiring loading. - Parameters: + Args: path (str): The file path to the YAML configuration file. Returns: @@ -66,7 +66,7 @@ def get_run_args_from_yaml(path: str) -> dict: # Initialize an empty dictionary to hold the extracted settings settings = {} - # List allowed NEPS run arguments with simple types (e.g., string, int). Parameters + # List allowed NePS run arguments with simple types (e.g., string, int). Parameters # like 'run_pipeline', 'preload_hooks', 'pipeline_space', # and 'searcher' are excluded due to needing specialized processing. expected_parameters = [ @@ -119,22 +119,15 @@ def get_run_args_from_yaml(path: str) -> dict: def config_loader(path: str) -> dict: """Loads a YAML file and returns the contents under the 'run_args' key. - Validates the existence and format of the YAML file and checks for the presence of - the 'run_args' as the only top level key. If any conditions are not met, - raises an - exception with a helpful message. - Args: path (str): Path to the YAML file. Returns: - dict: Contents under the 'run_args' key. + Content of the yaml (dict) Raises: FileNotFoundError: If the file at 'path' does not exist. ValueError: If the file is not a valid YAML. - KeyError: If 'run_args' key is missing. - KeyError: If 'run_args' is not the only top level key """ try: with open(path) as file: # noqa: PTH123 @@ -156,10 +149,13 @@ def extract_leaf_keys(d: dict, special_keys: dict | None = None) -> tuple[dict, Special keys (e.g.'run_pipeline') are also extracted if present and their corresponding values (dict) at any level in the nested structure. - :param d: The dictionary to extract values from. - :param special_keys: A dictionary to store values of special keys. - :return: A tuple containing the leaf keys dictionary and the dictionary for - special keys. + Args: + d (dict): The dictionary to extract values from. + special_keys (dict|None): A dictionary to store values of special keys. + + Returns: + A tuple containing the leaf keys dictionary and the dictionary for + special keys. """ if special_keys is None: special_keys = { @@ -191,14 +187,11 @@ def handle_special_argument_cases(settings: dict, special_configs: dict) -> None configurations like 'pre_load_hooks' which need individual processing or function loading. - Parameters: - - settings (dict): The dictionary to be updated with processed configurations. - - special_configs (dict): A dictionary containing configuration keys and values + Args: + settings (dict): The dictionary to be updated with processed configurations. + special_configs (dict): A dictionary containing configuration keys and values that require special processing. - Returns: - - None: The function modifies 'settings' in place. - """ # process special configs process_run_pipeline(RUN_PIPELINE, special_configs, settings) @@ -261,13 +254,13 @@ def process_searcher(key: str, special_configs: dict, settings: dict) -> None: Checks if the key exists in special_configs. If found, it processes the value based on its type. Updates settings with the processed searcher. - Parameters: - key (str): Key to look up in special_configs. - special_configs (dict): Dictionary of special configurations. - settings (dict): Dictionary to update with the processed searcher. + Args: + key (str): Key to look up in special_configs. + special_configs (dict): Dictionary of special configurations. + settings (dict): Dictionary to update with the processed searcher. Raises: - TypeError: If the value for the key is neither a string, Path, nor a dictionary. + TypeError: If the value for the key is neither a string, Path, nor a dictionary. """ if special_configs.get(key) is not None: searcher = special_configs[key] @@ -294,13 +287,13 @@ def process_searcher(key: str, special_configs: dict, settings: dict) -> None: def process_run_pipeline(key: str, special_configs: dict, settings: dict) -> None: """Processes the run pipeline configuration and updates the settings dictionary. - Parameters: - key (str): Key to look up in special_configs. - special_configs (dict): Dictionary of special configurations. - settings (dict): Dictionary to update with the processed function. + Args: + key (str): Key to look up in special_configs. + special_configs (dict): Dictionary of special configurations. + settings (dict): Dictionary to update with the processed function. Raises: - KeyError: If required keys ('path' and 'name') are missing in the config. + KeyError: If required keys ('path' and 'name') are missing in the config. """ if special_configs.get(key) is not None: config = special_configs[key] @@ -403,7 +396,7 @@ def check_run_args(settings: dict) -> None: TypeError for type mismatches. Args: - settings (dict): NEPS configuration settings. + settings (dict): NePS configuration settings. Raises: TypeError: For mismatched setting value types. @@ -462,14 +455,14 @@ def check_essential_arguments( searcher: BaseOptimizer | None, run_args: str | None, ) -> None: - """Validates essential NEPS configuration arguments. + """Validates essential NePS configuration arguments. Ensures 'run_pipeline', 'root_directory', 'pipeline_space', and either 'max_cost_total' or 'max_evaluation_total' are provided for NePS execution. Raises ValueError with missing argument details. Additionally, checks 'searcher' is a BaseOptimizer if 'pipeline_space' is absent. - Parameters: + Args: run_pipeline: Function for the pipeline execution. root_directory (str): Directory path for data storage. pipeline_space: search space for this run. @@ -499,19 +492,19 @@ def check_essential_arguments( def check_double_reference( func: Callable, func_arguments: dict, yaml_arguments: dict -) -> Any: +) -> None: """Checks if no argument is defined both via function arguments and YAML. - Parameters: - - func (Callable): The function to check arguments against. - - func_arguments (Dict): A dictionary containing the provided arguments to the - function and their values. - - yaml_arguments (Dict): A dictionary containing the arguments provided via a YAML - file. + Args: + func (Callable): The function to check arguments against. + func_arguments (Dict): A dictionary containing the provided arguments to the + function and their values. + yaml_arguments (Dict): A dictionary containing the arguments provided via a YAML + file. Raises: - - ValueError: If any provided argument is defined both via function arguments and the - YAML file. + ValueError: If any provided argument is defined both via function arguments and + the YAML file. """ sig = inspect.signature(func) From 4417942a8cef992a4d6ebdf7bf21ae0d6ca071c3 Mon Sep 17 00:00:00 2001 From: Daniel <63580393+danrgll@users.noreply.github.com> Date: Tue, 18 Jun 2024 13:47:06 +0200 Subject: [PATCH 25/29] code clean up + add notes to docs --- docs/reference/declarative_usage.md | 10 ++++++++++ neps/api.py | 2 +- neps/search_spaces/search_space.py | 2 +- neps/search_spaces/yaml_search_space_utils.py | 8 ++++---- 4 files changed, 16 insertions(+), 6 deletions(-) diff --git a/docs/reference/declarative_usage.md b/docs/reference/declarative_usage.md index bf175bab..6bad0610 100644 --- a/docs/reference/declarative_usage.md +++ b/docs/reference/declarative_usage.md @@ -3,6 +3,11 @@ ### Configuring with YAML Configure your experiments using a YAML file, which serves as a central reference for setting up your project. This approach simplifies sharing, reproducing and modifying configurations. + +!!! note + You can partially define arguments in the YAML file and partially provide the arguments directly to `neps.run`. + However, double referencing is not allowed. You cannot define the same argument in both places. + #### Simple YAML Example Below is a straightforward YAML configuration example for NePS covering the required arguments. === "config.yaml" @@ -72,6 +77,11 @@ through `neps.run`. For a detailed list of integrated optimizers, see [here](opt ### Customizing NePS optimizer Customize an internal NePS optimizer by specifying its parameters directly under the key `searcher` in the `config.yaml` file. + +!!! note + For `searcher_kwargs` of `neps.run`, the optimizer arguments passed via the YAML file and those passed directly via + `neps.run` will be merged. In this special case, if the same argument is referenced in both places, + `searcher_kwargs` will be prioritized and set for this argument. === "config.yaml" ```yaml --8<-- "docs/doc_yamls/customizing_neps_optimizer.yaml" diff --git a/neps/api.py b/neps/api.py index b909d4d3..9d84ce52 100644 --- a/neps/api.py +++ b/neps/api.py @@ -295,7 +295,7 @@ def run( if isinstance(searcher, BaseOptimizer): searcher_instance = searcher - searcher_info["searcher_name"] = "baseoptimizer" + searcher_info["searcher_name"] = "custom-baseoptimizer" searcher_info["searcher_alg"] = searcher.whoami() searcher_info["searcher_selection"] = "user-instantiation" searcher_info["neps_decision_tree"] = False diff --git a/neps/search_spaces/search_space.py b/neps/search_spaces/search_space.py index 889938fa..b02a7662 100644 --- a/neps/search_spaces/search_space.py +++ b/neps/search_spaces/search_space.py @@ -157,7 +157,7 @@ def pipeline_space_from_yaml( # noqa: C901, PLR0912 pipeline_space[name] = CategoricalParameter(**formatted_details) elif param_type == "const": # Constant parameter - formatted_details = formatting_const(details) + formatted_details = formatting_const(details) # type: ignore pipeline_space[name] = ConstantParameter(formatted_details) else: # Handle unknown parameter type diff --git a/neps/search_spaces/yaml_search_space_utils.py b/neps/search_spaces/yaml_search_space_utils.py index d3f67a83..9bcfcb11 100644 --- a/neps/search_spaces/yaml_search_space_utils.py +++ b/neps/search_spaces/yaml_search_space_utils.py @@ -184,7 +184,7 @@ def deduce_param_type(name: str, details: dict[str, int | str | float]) -> str: return param_type -def formatting_int(name: str, details: dict[str, str | int | float]): +def formatting_int(name: str, details: dict[str, str | int | float]) -> dict: """ Converts scientific notation values to integers. @@ -243,7 +243,7 @@ def formatting_int(name: str, details: dict[str, str | int | float]): return details -def formatting_float(name: str, details: dict[str, str | int | float]): +def formatting_float(name: str, details: dict[str, str | int | float]) -> dict: """ Converts scientific notation values to floats. @@ -284,7 +284,7 @@ def formatting_float(name: str, details: dict[str, str | int | float]): return details -def formatting_cat(name: str, details: dict[str, str | int | float]): +def formatting_cat(name: str, details: dict[str, str | int | float]) -> dict: """ This function ensures that the 'choices' key in the details is a list and attempts to convert any elements expressed in scientific notation to floats. It also handles @@ -328,7 +328,7 @@ def formatting_cat(name: str, details: dict[str, str | int | float]): return details -def formatting_const(details: str | int | float): +def formatting_const(details: str | int | float) -> str | int | float: """ Validates and converts a constant parameter. From f15d58fe8b084ff6659c8f62ffe3bfda8595ce24 Mon Sep 17 00:00:00 2001 From: Daniel <63580393+danrgll@users.noreply.github.com> Date: Tue, 18 Jun 2024 17:59:02 +0200 Subject: [PATCH 26/29] update declarative example --- neps_examples/declarative_usage/README.md | 45 ++++++++++------------- 1 file changed, 20 insertions(+), 25 deletions(-) diff --git a/neps_examples/declarative_usage/README.md b/neps_examples/declarative_usage/README.md index a2609e9d..5ebbb242 100644 --- a/neps_examples/declarative_usage/README.md +++ b/neps_examples/declarative_usage/README.md @@ -1,37 +1,32 @@ -# YAML Usage in NePS for MNIST Classification +# Declarative Usage in NePS for Neural Network Optimization -Within this folder, you'll find examples and templates tailored for optimizing neural networks on the MNIST dataset -through NePS, all configured via YAML files. These YAML files allow for the easy adjustment of experiment parameters -and search spaces, enabling you to fine-tune your classification model without directly modifying any Python code. -Below is a succinct overview of each provided file and its role in the optimization process: +This folder contains examples and templates for optimizing neural networks using NePS, configured via YAML files. +These configurations allow easy adjustments to experiment parameters and search spaces, enabling fine-tuning of your +models without modifying Python code. ### `hpo_example.py` -This Python script demonstrates how to utilize a YAML file to configure a hyperparameter optimization (HPO) task in -NePS. It's designed to show you how to integrate YAML configurations with your Python code to set up HPO experiments. -The script reads YAML files to get its parameters and search spaces, illustrating how to run HPO tasks with minimal -coding effort. Additionally, it includes an example of a run_pipeline function, guiding users on how to define their -model training and evaluation process within the optimization framework. -### `pipeline_space.yaml` +This Python script demonstrates how to integrate NePS with a neural network training pipeline for hyperparameter +optimization. It utilizes a YAML configuration file to set up and run the experiments. -This YAML file defines the search space for your pipeline, including the parameters and their ranges to be explored -during the search. It's structured to be easily readable and modifiable, allowing you to quickly adjust the search -space for your experiments. This file is referenced by the configuration YAML file `run_args.yaml` to dynamically load -the search configuration. +```python +--8<-- "neps_examples/declarative_usage/hpo_example.py" +``` -### `run_args.yaml` +### `config.yaml` -This file showcases an example set of NePS arguments, simplifying its usage. By editing this file, -users can customize their experiments without needing to modify the Python script. +This YAML file defines the NePS arguments for the experiment. By editing this file, users can customize their +experiments without modifying the Python script. -### `run_args_alternative.yaml` - -An alternative set of arguments. This file has listed the full set of arguments and includes explanations for each of -them. Additionally, it demonstrates how you can structure your YAML file for improved clarity. +```yaml +--8<-- "neps_examples/declarative_usage/config.yaml" +``` ## Quick Start Guide -1. **Review the YAML Files:** Start by looking at `pipeline_space.yaml`, `run_args.yaml`, and `run_args_alternative.yaml` to understand the available configurations and how they're structured. -2. **Run the Example Script:** Execute `hpo_example.py`, specifying which YAML file to use for the run arguments. This will initiate an HPO task based on your YAML configurations. -3. **Modify YAML Files:** Experiment with adjusting the parameters in the YAML files to see how changes affect your search experiments. This is a great way to learn about the impact of different configurations on your results. +1. **Review the YAML File:** Examine `config.yaml` to understand the available configurations and how they are structured. +2. **Run the Example Script:** Execute hpo_example.py, by providing `config.yaml` via the run_args agrument to NePS. + This will initiate a hyperparameter optimization task based on your YAML configurations. +3. **Modify YAML File:** Experiment with adjusting the parameters in the YAML file to see how changes affect your + search experiments. This is a great way to learn about the impact of different configurations on your results. By following these steps and utilizing the provided YAML files, you'll be able to efficiently set up, run, and modify your NePS experiments. Enjoy the flexibility and simplicity that comes with managing your experiment configurations in YAML! From 3cf2b7395491410a5997abb648eb13b9c4544b7e Mon Sep 17 00:00:00 2001 From: Daniel <63580393+danrgll@users.noreply.github.com> Date: Wed, 19 Jun 2024 09:10:25 +0200 Subject: [PATCH 27/29] fix test --- neps/api.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/neps/api.py b/neps/api.py index 9d84ce52..b909d4d3 100644 --- a/neps/api.py +++ b/neps/api.py @@ -295,7 +295,7 @@ def run( if isinstance(searcher, BaseOptimizer): searcher_instance = searcher - searcher_info["searcher_name"] = "custom-baseoptimizer" + searcher_info["searcher_name"] = "baseoptimizer" searcher_info["searcher_alg"] = searcher.whoami() searcher_info["searcher_selection"] = "user-instantiation" searcher_info["neps_decision_tree"] = False From 1a530e59351d0cdf4044e1195ae63160a7d6cc87 Mon Sep 17 00:00:00 2001 From: Daniel <63580393+danrgll@users.noreply.github.com> Date: Fri, 21 Jun 2024 14:47:22 +0200 Subject: [PATCH 28/29] change boolean values in yamls from [True, False] to [true, false] --- docs/doc_yamls/customizing_neps_optimizer.yaml | 2 +- docs/doc_yamls/defining_hooks.yaml | 4 ++-- docs/doc_yamls/full_configuration_template.yaml | 10 +++++----- docs/doc_yamls/loading_own_optimizer.yaml | 2 +- docs/doc_yamls/outsourcing_optimizer.yaml | 2 +- docs/doc_yamls/pipeline_space.yaml | 4 ++-- docs/doc_yamls/simple_example.yaml | 4 ++-- .../simple_example_including_run_pipeline.yaml | 4 ++-- docs/reference/pipeline_space.md | 2 +- neps_examples/declarative_usage/config.yaml | 6 +++--- tests/test_yaml_run_args/run_args_full.yaml | 8 ++++---- tests/test_yaml_run_args/run_args_full_same_level.yaml | 8 ++++---- tests/test_yaml_run_args/run_args_invalid_key.yaml | 8 ++++---- tests/test_yaml_run_args/run_args_invalid_type.yaml | 4 ++-- tests/test_yaml_run_args/run_args_key_missing.yaml | 8 ++++---- .../run_args_optional_loading_format.yaml | 8 ++++---- tests/test_yaml_run_args/run_args_partial.yaml | 6 +++--- 17 files changed, 45 insertions(+), 45 deletions(-) diff --git a/docs/doc_yamls/customizing_neps_optimizer.yaml b/docs/doc_yamls/customizing_neps_optimizer.yaml index a047be6d..a176dc74 100644 --- a/docs/doc_yamls/customizing_neps_optimizer.yaml +++ b/docs/doc_yamls/customizing_neps_optimizer.yaml @@ -7,7 +7,7 @@ pipeline_space: learning_rate: lower: 1e-5 upper: 1e-1 - log: True # Log scale for learning rate + log: true # Log scale for learning rate optimizer: choices: [adam, sgd, adamw] epochs: 50 diff --git a/docs/doc_yamls/defining_hooks.yaml b/docs/doc_yamls/defining_hooks.yaml index 640d2004..d63c3ce5 100644 --- a/docs/doc_yamls/defining_hooks.yaml +++ b/docs/doc_yamls/defining_hooks.yaml @@ -7,11 +7,11 @@ pipeline_space: learning_rate: lower: 1e-5 upper: 1e-1 - log: True # Log scale for learning rate + log: true # Log scale for learning rate epochs: lower: 5 upper: 20 - is_fidelity: True + is_fidelity: true optimizer: choices: [adam, sgd, adamw] batch_size: 64 diff --git a/docs/doc_yamls/full_configuration_template.yaml b/docs/doc_yamls/full_configuration_template.yaml index 84fb7523..6b28ccf4 100644 --- a/docs/doc_yamls/full_configuration_template.yaml +++ b/docs/doc_yamls/full_configuration_template.yaml @@ -7,11 +7,11 @@ pipeline_space: learning_rate: lower: 1e-5 upper: 1e-1 - log: True + log: true epochs: lower: 5 upper: 20 - is_fidelity: True + is_fidelity: true optimizer: choices: [adam, sgd, adamw] batch_size: 64 @@ -21,14 +21,14 @@ max_evaluations_total: 20 # Budget max_cost_total: # Debug and Monitoring -overwrite_working_directory: True -post_run_summary: False +overwrite_working_directory: true +post_run_summary: false development_stage_id: task_id: # Parallelization Setup max_evaluations_per_run: -continue_until_max_evaluation_completed: False +continue_until_max_evaluation_completed: false # Error Handling loss_value_on_error: diff --git a/docs/doc_yamls/loading_own_optimizer.yaml b/docs/doc_yamls/loading_own_optimizer.yaml index 3058f936..b23cd082 100644 --- a/docs/doc_yamls/loading_own_optimizer.yaml +++ b/docs/doc_yamls/loading_own_optimizer.yaml @@ -7,7 +7,7 @@ pipeline_space: learning_rate: lower: 1e-5 upper: 1e-1 - log: True # Log scale for learning rate + log: true # Log scale for learning rate optimizer: choices: [adam, sgd, adamw] epochs: 50 diff --git a/docs/doc_yamls/outsourcing_optimizer.yaml b/docs/doc_yamls/outsourcing_optimizer.yaml index b0fe734b..6cdf3a2c 100644 --- a/docs/doc_yamls/outsourcing_optimizer.yaml +++ b/docs/doc_yamls/outsourcing_optimizer.yaml @@ -7,7 +7,7 @@ pipeline_space: learning_rate: lower: 1e-5 upper: 1e-1 - log: True # Log scale for learning rate + log: true # Log scale for learning rate optimizer: choices: [adam, sgd, adamw] epochs: 50 diff --git a/docs/doc_yamls/pipeline_space.yaml b/docs/doc_yamls/pipeline_space.yaml index bae58406..939a5358 100644 --- a/docs/doc_yamls/pipeline_space.yaml +++ b/docs/doc_yamls/pipeline_space.yaml @@ -2,13 +2,13 @@ learning_rate: lower: 1e-5 upper: 1e-1 - log: True # Log scale for learning rate + log: true # Log scale for learning rate default: 1e-2 default_confidence: "medium" epochs: lower: 5 upper: 20 - is_fidelity: True + is_fidelity: true dropout_rate: lower: 0.1 upper: 0.5 diff --git a/docs/doc_yamls/simple_example.yaml b/docs/doc_yamls/simple_example.yaml index 5c4758a2..35cde121 100644 --- a/docs/doc_yamls/simple_example.yaml +++ b/docs/doc_yamls/simple_example.yaml @@ -3,11 +3,11 @@ pipeline_space: learning_rate: lower: 1e-5 upper: 1e-1 - log: True # Log scale for learning rate + log: true # Log scale for learning rate epochs: lower: 5 upper: 20 - is_fidelity: True + is_fidelity: true optimizer: choices: [adam, sgd, adamw] batch_size: 64 diff --git a/docs/doc_yamls/simple_example_including_run_pipeline.yaml b/docs/doc_yamls/simple_example_including_run_pipeline.yaml index 9c40bfad..afe8e9b8 100644 --- a/docs/doc_yamls/simple_example_including_run_pipeline.yaml +++ b/docs/doc_yamls/simple_example_including_run_pipeline.yaml @@ -7,11 +7,11 @@ pipeline_space: learning_rate: lower: 1e-5 upper: 1e-1 - log: True # Log scale for learning rate + log: true # Log scale for learning rate epochs: lower: 5 upper: 20 - is_fidelity: True + is_fidelity: true optimizer: choices: [adam, sgd, adamw] batch_size: 64 diff --git a/docs/reference/pipeline_space.md b/docs/reference/pipeline_space.md index 172f05dd..0458f60d 100644 --- a/docs/reference/pipeline_space.md +++ b/docs/reference/pipeline_space.md @@ -103,7 +103,7 @@ Create a YAML file (e.g., `./pipeline_space.yaml`) with the parameter definition type: int lower: 3 upper: 30 - is_fidelity: True + is_fidelity: true optimizer: type: categorical diff --git a/neps_examples/declarative_usage/config.yaml b/neps_examples/declarative_usage/config.yaml index bf404a5a..83776cc5 100644 --- a/neps_examples/declarative_usage/config.yaml +++ b/neps_examples/declarative_usage/config.yaml @@ -1,8 +1,8 @@ experiment: root_directory: "results/example_run" max_evaluations_total: 20 - overwrite_working_directory: True - post_run_summary: True + overwrite_working_directory: true + post_run_summary: true development_stage_id: "beta" pipeline_space: @@ -10,7 +10,7 @@ pipeline_space: learning_rate: lower: 1e-5 upper: 1e-1 - log: True + log: true num_layers: lower: 1 upper: 5 diff --git a/tests/test_yaml_run_args/run_args_full.yaml b/tests/test_yaml_run_args/run_args_full.yaml index cc573995..1ec96140 100644 --- a/tests/test_yaml_run_args/run_args_full.yaml +++ b/tests/test_yaml_run_args/run_args_full.yaml @@ -9,19 +9,19 @@ budget: max_cost_total: 3 monitoring: - overwrite_working_directory: True - post_run_summary: True + overwrite_working_directory: true + post_run_summary: true development_stage_id: "Early_Stage" task_id: 4 parallelization_setup: max_evaluations_per_run: 5 - continue_until_max_evaluation_completed: True + continue_until_max_evaluation_completed: true error_handling: loss_value_on_error: 4.2 cost_value_on_error: 3.7 - ignore_errors: True + ignore_errors: true search: searcher: diff --git a/tests/test_yaml_run_args/run_args_full_same_level.yaml b/tests/test_yaml_run_args/run_args_full_same_level.yaml index b6690a1f..b920533e 100644 --- a/tests/test_yaml_run_args/run_args_full_same_level.yaml +++ b/tests/test_yaml_run_args/run_args_full_same_level.yaml @@ -5,15 +5,15 @@ pipeline_space: "tests/test_yaml_run_args/pipeline_space.yaml" root_directory: "test_yaml" max_evaluations_total: 20 max_cost_total: 4.2 -overwrite_working_directory: True -post_run_summary: False +overwrite_working_directory: true +post_run_summary: false development_stage_id: 9 task_id: 2.0 max_evaluations_per_run: 5 -continue_until_max_evaluation_completed: True +continue_until_max_evaluation_completed: true loss_value_on_error: 2.4 cost_value_on_error: 2.1 -ignore_errors: False +ignore_errors: false searcher: strategy: "bayesian_optimization" initial_design_size: 5 diff --git a/tests/test_yaml_run_args/run_args_invalid_key.yaml b/tests/test_yaml_run_args/run_args_invalid_key.yaml index f9095abd..b9c0ff2d 100644 --- a/tests/test_yaml_run_args/run_args_invalid_key.yaml +++ b/tests/test_yaml_run_args/run_args_invalid_key.yaml @@ -9,19 +9,19 @@ budget: max_cost_total: 3 monitoring: - overwrite_working_directory: True - post_run_summary: True + overwrite_working_directory: true + post_run_summary: true development_stage_id: "Early_Stage" task_id: 4 parallelization_setup: max_evaluations_per_run: 5 - continue_until_max_evaluation_completed: True + continue_until_max_evaluation_completed: true error_handling: loss_value_on_error: 4.2 cost_value_on_error: 3.7 - ignore_errors: True + ignore_errors: true search: searcher: diff --git a/tests/test_yaml_run_args/run_args_invalid_type.yaml b/tests/test_yaml_run_args/run_args_invalid_type.yaml index 065dd967..1c85b8cb 100644 --- a/tests/test_yaml_run_args/run_args_invalid_type.yaml +++ b/tests/test_yaml_run_args/run_args_invalid_type.yaml @@ -6,14 +6,14 @@ budget: max_cost_total: monitoring: - overwrite_working_directory: True + overwrite_working_directory: true post_run_summary: Falsee # Error development_stage_id: "None" task_id: "None" parallelization_setup: max_evaluations_per_run: None - continue_until_max_evaluation_completed: False + continue_until_max_evaluation_completed: false error_handling: loss_value_on_error: None diff --git a/tests/test_yaml_run_args/run_args_key_missing.yaml b/tests/test_yaml_run_args/run_args_key_missing.yaml index 8394c5a0..ae27c608 100644 --- a/tests/test_yaml_run_args/run_args_key_missing.yaml +++ b/tests/test_yaml_run_args/run_args_key_missing.yaml @@ -5,15 +5,15 @@ pipeline_space: "tests/test_yaml_run_args/pipeline_space.yaml" root_directory: "test_yaml" max_evaluations_total: 20 max_cost_total: 4.2 -overwrite_working_directory: True -post_run_summary: False +overwrite_working_directory: true +post_run_summary: false development_stage_id: 9 task_id: 2.0 max_evaluations_per_run: 5 -continue_until_max_evaluation_completed: True +continue_until_max_evaluation_completed: true loss_value_on_error: 2.4 cost_value_on_error: 2.1 -ignore_errors: False +ignore_errors: false searcher: strategy: "bayesian_optimization" initial_design_size: 5 diff --git a/tests/test_yaml_run_args/run_args_optional_loading_format.yaml b/tests/test_yaml_run_args/run_args_optional_loading_format.yaml index e4f2178a..26bdad83 100644 --- a/tests/test_yaml_run_args/run_args_optional_loading_format.yaml +++ b/tests/test_yaml_run_args/run_args_optional_loading_format.yaml @@ -7,15 +7,15 @@ pipeline_space: # Optional loading root_directory: "test_yaml" max_evaluations_total: 20 max_cost_total: 4.2 -overwrite_working_directory: True -post_run_summary: False +overwrite_working_directory: true +post_run_summary: false development_stage_id: 9 task_id: max_evaluations_per_run: 5 -continue_until_max_evaluation_completed: True +continue_until_max_evaluation_completed: true loss_value_on_error: 2.4 cost_value_on_error: 2.1 -ignore_errors: False +ignore_errors: false searcher: # Optional Loading path: "neps/optimizers/bayesian_optimization/optimizer.py" name: BayesianOptimization diff --git a/tests/test_yaml_run_args/run_args_partial.yaml b/tests/test_yaml_run_args/run_args_partial.yaml index e07ed17a..c5226a64 100644 --- a/tests/test_yaml_run_args/run_args_partial.yaml +++ b/tests/test_yaml_run_args/run_args_partial.yaml @@ -9,14 +9,14 @@ budget: max_cost_total: monitoring: - overwrite_working_directory: True - post_run_summary: False + overwrite_working_directory: true + post_run_summary: false development_stage_id: None task_id: None parallelization_setup: max_evaluations_per_run: None - continue_until_max_evaluation_completed: False + continue_until_max_evaluation_completed: false search: searcher: From 94068963440f83f7a39327ec67cc929fbcd68f66 Mon Sep 17 00:00:00 2001 From: Daniel <63580393+danrgll@users.noreply.github.com> Date: Fri, 21 Jun 2024 15:01:02 +0200 Subject: [PATCH 29/29] fix path reference --- neps/api.py | 2 +- tests/test_neps_api/testing_scripts/user_yaml_neps.py | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/neps/api.py b/neps/api.py index b909d4d3..40f5ad7f 100644 --- a/neps/api.py +++ b/neps/api.py @@ -317,7 +317,7 @@ def run( # Check to verify if the target directory contains history of another optimizer state # This check is performed only when the `searcher` is built during the run - if not isinstance(searcher, (BaseOptimizer, str, dict)): + if not isinstance(searcher, (BaseOptimizer, str, dict, Path)): raise ValueError( f"Unrecognized `searcher` of type {type(searcher)}. Not str or BaseOptimizer." ) diff --git a/tests/test_neps_api/testing_scripts/user_yaml_neps.py b/tests/test_neps_api/testing_scripts/user_yaml_neps.py index 03fd2afc..5d862d9d 100644 --- a/tests/test_neps_api/testing_scripts/user_yaml_neps.py +++ b/tests/test_neps_api/testing_scripts/user_yaml_neps.py @@ -1,6 +1,6 @@ import logging import os - +from pathlib import Path import neps pipeline_space = dict( @@ -19,7 +19,7 @@ def run_pipeline(val1, val2): # Testing using created yaml with api script_directory = os.path.dirname(os.path.abspath(__file__)) parent_directory = os.path.join(script_directory, os.pardir) -searcher_path = os.path.join(parent_directory, "testing_yaml/optimizer_test") +searcher_path = Path(parent_directory) / "testing_yaml" / "optimizer_test" neps.run( run_pipeline=run_pipeline, pipeline_space=pipeline_space,