diff --git a/docs/doc_yamls/run_pipeline.py b/docs/doc_yamls/run_pipeline.py index cfea0feb..29b33e72 100644 --- a/docs/doc_yamls/run_pipeline.py +++ b/docs/doc_yamls/run_pipeline.py @@ -2,6 +2,6 @@ def example_pipeline(learning_rate, optimizer, epochs): model = initialize_model() - training_loss = train_model(model, optimizer, learning_rate, epochs) - evaluation_loss = evaluate_model(model) - return {"loss": evaluation_loss, "training_loss": training_loss} + training_objective_to_minimize = train_model(model, optimizer, learning_rate, epochs) + evaluation_objective_to_minimize = evaluate_model(model) + return {"objective_to_minimize": evaluation_objective_to_minimize, "training_objective_to_minimize": training_objective_to_minimize} diff --git a/docs/doc_yamls/run_pipeline_architecture.py b/docs/doc_yamls/run_pipeline_architecture.py index f113f5f5..c3314489 100644 --- a/docs/doc_yamls/run_pipeline_architecture.py +++ b/docs/doc_yamls/run_pipeline_architecture.py @@ -19,6 +19,6 @@ def example_pipeline(architecture, optimizer, learning_rate): nn.Flatten(), nn.Linear(base_channels * out_channels_factor, n_classes), ) - training_loss = train_model(model, optimizer, learning_rate) - evaluation_loss = evaluate_model(model) - return {"loss": evaluation_loss, "training_loss": training_loss} + training_objective_to_minimize = train_model(model, optimizer, learning_rate) + evaluation_objective_to_minimize = evaluate_model(model) + return {"objective_to_minimize": evaluation_objective_to_minimize, "training_objective_to_minimize": training_objective_to_minimize} diff --git a/docs/doc_yamls/run_pipeline_big_search_space.py b/docs/doc_yamls/run_pipeline_big_search_space.py index 542283f0..346fe3bb 100644 --- a/docs/doc_yamls/run_pipeline_big_search_space.py +++ b/docs/doc_yamls/run_pipeline_big_search_space.py @@ -1,6 +1,6 @@ def example_pipeline(learning_rate, optimizer, epochs, batch_size, dropout_rate): model = initialize_model(dropout_rate) - training_loss = train_model(model, optimizer, learning_rate, epochs, batch_size) - evaluation_loss = evaluate_model(model) - return {"loss": evaluation_loss, "training_loss": training_loss} + training_objective_to_minimize = train_model(model, optimizer, learning_rate, epochs, batch_size) + evaluation_objective_to_minimize = evaluate_model(model) + return {"objective_to_minimize": evaluation_objective_to_minimize, "training_objective_to_minimize": training_objective_to_minimize} diff --git a/docs/doc_yamls/run_pipeline_extended.py b/docs/doc_yamls/run_pipeline_extended.py index c891f4d9..7a57f071 100644 --- a/docs/doc_yamls/run_pipeline_extended.py +++ b/docs/doc_yamls/run_pipeline_extended.py @@ -1,6 +1,6 @@ def example_pipeline(learning_rate, optimizer, epochs, batch_size): model = initialize_model() - training_loss = train_model(model, optimizer, learning_rate, epochs, batch_size) - evaluation_loss = evaluate_model(model) - return {"loss": evaluation_loss, "training_loss": training_loss} + training_objective_to_minimize = train_model(model, optimizer, learning_rate, epochs, batch_size) + evaluation_objective_to_minimize = evaluate_model(model) + return {"objective_to_minimize": evaluation_objective_to_minimize, "training_objective_to_minimize": training_objective_to_minimize} diff --git a/neps/api.py b/neps/api.py index a140a7f6..215fb6ae 100644 --- a/neps/api.py +++ b/neps/api.py @@ -26,7 +26,7 @@ def run( - run_pipeline: Callable | None = Default(None), + evaluate_pipeline: Callable | None = Default(None), root_directory: str | Path | None = Default(None), pipeline_space: ( dict[str, Parameter] | str | Path | CS.ConfigurationSpace | None @@ -41,7 +41,7 @@ def run( continue_until_max_evaluation_completed: bool = Default(False), max_cost_total: int | float | None = Default(None), ignore_errors: bool = Default(False), - loss_value_on_error: None | float = Default(None), + objective_to_minimize_value_on_error: None | float = Default(None), cost_value_on_error: None | float = Default(None), pre_load_hooks: Iterable | None = Default(None), searcher: ( @@ -68,14 +68,14 @@ def run( the multiple calls to run(.) will be independent. Args: - run_pipeline: The objective function to minimize. + evaluate_pipeline: The objective function to minimize. pipeline_space: The search space to minimize over. root_directory: The directory to save progress to. This is also used to synchronize multiple calls to run(.) for parallelization. run_args: An option for providing the optimization settings e.g. max_evaluations_total in a YAML file. overwrite_working_directory: If true, delete the working directory at the start of - the run. This is, e.g., useful when debugging a run_pipeline function. + the run. This is, e.g., useful when debugging a evaluate_pipeline function. post_run_summary: If True, creates a csv file after each worker is done, holding summary information about the configs and results. development_stage_id: ID for the current development stage. Only needed if @@ -89,13 +89,13 @@ def run( max_evaluations_total have been completed. This is only relevant in the parallel setting. max_cost_total: No new evaluations will start when this cost is exceeded. Requires - returning a cost in the run_pipeline function, e.g., + returning a cost in the evaluate_pipeline function, e.g., `return dict(loss=loss, cost=cost)`. ignore_errors: Ignore hyperparameter settings that threw an error and do not raise an error. Error configs still count towards max_evaluations_total. - loss_value_on_error: Setting this and cost_value_on_error to any float will - supress any error and will use given loss value instead. default: None - cost_value_on_error: Setting this and loss_value_on_error to any float will + objective_to_minimize_value_on_error: Setting this and cost_value_on_error to any float will + supress any error and will use given objective_to_minimize value instead. default: None + cost_value_on_error: Setting this and objective_to_minimize_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. Can be a string identifier, an @@ -111,7 +111,7 @@ def run( Example: >>> import neps - >>> def run_pipeline(some_parameter: float): + >>> def evaluate_pipeline(some_parameter: float): >>> validation_error = -some_parameter >>> return validation_error @@ -119,7 +119,7 @@ def run( >>> logging.basicConfig(level=logging.INFO) >>> neps.run( - >>> run_pipeline=run_pipeline, + >>> evaluate_pipeline=evaluate_pipeline, >>> pipeline_space=pipeline_space, >>> root_directory="usage_example", >>> max_evaluations_total=5, @@ -193,7 +193,7 @@ def run( pipeline_space=settings.pipeline_space, max_cost_total=settings.max_cost_total, ignore_errors=settings.ignore_errors, - loss_value_on_error=settings.loss_value_on_error, + objective_to_minimize_value_on_error=settings.objective_to_minimize_value_on_error, cost_value_on_error=settings.cost_value_on_error, searcher=settings.searcher, **settings.searcher_kwargs, @@ -223,7 +223,7 @@ def run( ) _launch_runtime( - evaluation_fn=settings.run_pipeline, + evaluation_fn=settings.evaluate_pipeline, optimizer=searcher_instance, optimizer_info=searcher_info, max_cost_total=settings.max_cost_total, @@ -231,7 +231,7 @@ def run( max_evaluations_total=settings.max_evaluations_total, max_evaluations_for_worker=settings.max_evaluations_per_run, continue_until_max_evaluation_completed=settings.continue_until_max_evaluation_completed, - loss_value_on_error=settings.loss_value_on_error, + objective_to_minimize_value_on_error=settings.objective_to_minimize_value_on_error, cost_value_on_error=settings.cost_value_on_error, ignore_errors=settings.ignore_errors, overwrite_optimization_dir=settings.overwrite_working_directory, @@ -266,7 +266,7 @@ def _run_args( ) = None, max_cost_total: int | float | None = None, ignore_errors: bool = False, - loss_value_on_error: None | float = None, + objective_to_minimize_value_on_error: None | float = None, cost_value_on_error: None | float = None, searcher: ( Literal[ @@ -406,7 +406,7 @@ def _run_args( searcher_config.update( { - "loss_value_on_error": loss_value_on_error, + "objective_to_minimize_value_on_error": objective_to_minimize_value_on_error, "cost_value_on_error": cost_value_on_error, "ignore_errors": ignore_errors, } @@ -416,7 +416,7 @@ def _run_args( SearcherMapping, searcher_alg, "searcher", as_class=True )( pipeline_space=pipeline_space, - budget=max_cost_total, # TODO: use max_cost_total everywhere + max_cost_total=max_cost_total, # TODO: use max_cost_total everywhere **searcher_config, ) diff --git a/neps/optimizers/base_optimizer.py b/neps/optimizers/base_optimizer.py index 898508c7..a7ce9bd1 100644 --- a/neps/optimizers/base_optimizer.py +++ b/neps/optimizers/base_optimizer.py @@ -14,9 +14,9 @@ from neps.utils.types import ERROR, ResultDict -def _get_loss( +def _get_objective_to_minimize( result: ERROR | ResultDict | float, - loss_value_on_error: float | None = None, + objective_to_minimize_value_on_error: float | None = None, *, ignore_errors: bool = False, ) -> ERROR | float: @@ -24,19 +24,20 @@ def _get_loss( if ignore_errors: return "error" - if loss_value_on_error is not None: - return loss_value_on_error + if objective_to_minimize_value_on_error is not None: + return objective_to_minimize_value_on_error raise ValueError( - "An error happened during the execution of your run_pipeline function." + "An error happened during the execution of your evaluate_pipeline function." " You have three options: 1. If the error is expected and corresponds to" - " a loss value in your application (e.g., 0% accuracy), you can set" - " loss_value_on_error to some float. 2. If sometimes your pipeline" - " crashes randomly, you can set ignore_errors=True. 3. Fix your error." + " an objective_to_minimize value in your application (e.g., 0% accuracy)," + " you can set objective_to_minimize_value_on_error to some float. 2. If " + " sometimes your pipeline crashes randomly, you can set ignore_errors=True." + " 3. Fix your error." ) if isinstance(result, dict): - return float(result["loss"]) + return float(result["objective_to_minimize"]) assert isinstance(result, float) return float(result) @@ -54,9 +55,9 @@ def _get_cost( if cost_value_on_error is None: raise ValueError( - "An error happened during the execution of your run_pipeline function." - " You have three options: 1. If the error is expected and corresponds to" - " a cost value in your application, you can set" + "An error happened during the execution of your evaluate_pipeline" + " function. You have three options: 1. If the error is expected and" + " corresponds to a cost value in your application, you can set" " cost_value_on_error to some float. 2. If sometimes your pipeline" " crashes randomly, you can set ignore_errors=True. 3. Fix your error." ) @@ -88,8 +89,8 @@ def __init__( pipeline_space: SearchSpace, patience: int = 50, logger: logging.Logger | None = None, - budget: int | float | None = None, - loss_value_on_error: float | None = None, + max_cost_total: int | float | None = None, + objective_to_minimize_value_on_error: float | None = None, cost_value_on_error: float | None = None, learning_curve_on_error: float | list[float] | None = None, ignore_errors: bool = False, @@ -97,11 +98,11 @@ def __init__( if patience < 1: raise ValueError("Patience should be at least 1") - self.budget = budget + self.max_cost_total = max_cost_total self.pipeline_space = pipeline_space self.patience = patience self.logger = logger or logging.getLogger("neps") - self.loss_value_on_error = loss_value_on_error + self.objective_to_minimize_value_on_error = objective_to_minimize_value_on_error self.cost_value_on_error = cost_value_on_error self.learning_curve_on_error = learning_curve_on_error self.ignore_errors = ignore_errors @@ -110,13 +111,13 @@ def __init__( def ask( self, trials: Mapping[str, Trial], - budget_info: BudgetInfo | None, + max_cost_total_info: BudgetInfo | None, ) -> SampledConfig: """Sample a new configuration. Args: trials: All of the trials that are known about. - budget_info: information about the budget + max_cost_total_info: information about the max_cost_total Returns: SampledConfig: a sampled configuration @@ -124,20 +125,27 @@ def ask( """ ... - def get_loss(self, result: ERROR | ResultDict | float | Report) -> float | ERROR: - """Calls result.utils.get_loss() and passes the error handling through. - Please use self.get_loss() instead of get_loss() in all optimizer classes. + def get_objective_to_minimize( + self, result: ERROR | ResultDict | float | Report + ) -> float | ERROR: + """Calls result.utils.get_objective_to_minimize() and passes the error handling + through. Please use self.get_objective_to_minimize() instead of + get_objective_to_minimize() in all optimizer classes. """ # TODO(eddiebergman): This is a forward change for whenever we can have optimizers # use `Trial` and `Report`, they already take care of this and save having to do - # this `_get_loss` at every call. We can also then just use `None` instead of - # the string `"error"` + # this `_get_objective_to_minimize` at every call. We can also then just use + # `None` instead of the string `"error"` if isinstance(result, Report): - return result.loss if result.loss is not None else "error" + return ( + result.objective_to_minimize + if result.objective_to_minimize is not None + else "error" + ) - return _get_loss( + return _get_objective_to_minimize( result, - loss_value_on_error=self.loss_value_on_error, + objective_to_minimize_value_on_error=self.objective_to_minimize_value_on_error, ignore_errors=self.ignore_errors, ) @@ -147,9 +155,13 @@ def get_cost(self, result: ERROR | ResultDict | float | Report) -> float | ERROR """ # TODO(eddiebergman): This is a forward change for whenever we can have optimizers # use `Trial` and `Report`, they already take care of this and save having to do - # this `_get_loss` at every call + # this `_get_objective_to_minimize` at every call if isinstance(result, Report): - return result.loss if result.loss is not None else "error" + return ( + result.objective_to_minimize + if result.objective_to_minimize is not None + else "error" + ) return _get_cost( result, diff --git a/neps/optimizers/bayesian_optimization/acquisition_functions/cost_cooling.py b/neps/optimizers/bayesian_optimization/acquisition_functions/cost_cooling.py index 4095d6f2..46fe1309 100644 --- a/neps/optimizers/bayesian_optimization/acquisition_functions/cost_cooling.py +++ b/neps/optimizers/bayesian_optimization/acquisition_functions/cost_cooling.py @@ -41,14 +41,14 @@ def apply_cost_cooling( def cost_cooled_acq( acq_fn: AcquisitionFunction, model: GPyTorchModel, - used_budget_percentage: float, + used_max_cost_total_percentage: float, ) -> WeightedAcquisition: - assert 0 <= used_budget_percentage <= 1 + assert 0 <= used_max_cost_total_percentage <= 1 return WeightedAcquisition( acq=acq_fn, apply_weight=partial( apply_cost_cooling, cost_model=model, - alpha=1 - used_budget_percentage, + alpha=1 - used_max_cost_total_percentage, ), ) diff --git a/neps/optimizers/bayesian_optimization/models/ftpfn.py b/neps/optimizers/bayesian_optimization/models/ftpfn.py index c5f206a5..2990b095 100644 --- a/neps/optimizers/bayesian_optimization/models/ftpfn.py +++ b/neps/optimizers/bayesian_optimization/models/ftpfn.py @@ -164,7 +164,7 @@ def encode_ftpfn( device=device, dtype=dtype, ) - train_budgets = budget_domain.cast( + train_max_cost_total = budget_domain.cast( train_fidelities, frm=space.fidelity.domain, dtype=dtype ) @@ -174,7 +174,11 @@ def encode_ftpfn( [ pending_value if trial.report is None - else (error_value if trial.report.loss is None else trial.report.loss) + else ( + error_value + if trial.report.objective_to_minimize is None + else trial.report.objective_to_minimize + ) for trial in trials.values() ], device=device, @@ -188,7 +192,7 @@ def encode_ftpfn( ) maximize_ys = 1 - minimize_ys x_train = torch.cat( - [ids.unsqueeze(1), train_budgets.unsqueeze(1), train_configs], dim=1 + [ids.unsqueeze(1), train_max_cost_total.unsqueeze(1), train_configs], dim=1 ) return x_train, maximize_ys diff --git a/neps/optimizers/bayesian_optimization/models/gp.py b/neps/optimizers/bayesian_optimization/models/gp.py index 681dbb71..2210e44b 100644 --- a/neps/optimizers/bayesian_optimization/models/gp.py +++ b/neps/optimizers/bayesian_optimization/models/gp.py @@ -243,8 +243,10 @@ def encode_trials_for_gp( train_configs.append(trial.config) - loss = trial.report.loss - train_losses.append(torch.nan if loss is None else loss) + objective_to_minimize = trial.report.objective_to_minimize + train_losses.append( + torch.nan if objective_to_minimize is None else objective_to_minimize + ) cost = trial.report.cost train_costs.append(torch.nan if cost is None else cost) @@ -399,7 +401,7 @@ def fit_and_acquire_from_gp( acquisition = cost_cooled_acq( acq_fn=acquisition, model=cost_gp, - used_budget_percentage=cost_percentage_used, + used_max_cost_total_percentage=cost_percentage_used, ) _n = n_candidates_required if n_candidates_required is not None else 1 diff --git a/neps/optimizers/bayesian_optimization/optimizer.py b/neps/optimizers/bayesian_optimization/optimizer.py index bf491e1c..72ded5e0 100644 --- a/neps/optimizers/bayesian_optimization/optimizer.py +++ b/neps/optimizers/bayesian_optimization/optimizer.py @@ -69,9 +69,9 @@ def __init__( device: torch.device | None = None, encoder: ConfigEncoder | None = None, seed: int | None = None, - budget: Any | None = None, # TODO: remove + max_cost_total: Any | None = None, # TODO: remove surrogate_model: Any | None = None, # TODO: remove - loss_value_on_error: Any | None = None, # TODO: remove + objective_to_minimize_value_on_error: Any | None = None, # TODO: remove cost_value_on_error: Any | None = None, # TODO: remove ignore_errors: Any | None = None, # TODO: remove ): @@ -134,7 +134,7 @@ def __init__( def ask( self, trials: Mapping[str, Trial], - budget_info: BudgetInfo | None = None, + max_cost_total_info: BudgetInfo | None = None, ) -> SampledConfig: n_sampled = len(trials) config_id = str(n_sampled + 1) @@ -164,14 +164,16 @@ def ask( cost_percent = None if self.use_cost: - if budget_info is None: + if max_cost_total_info is None: raise ValueError( "Must provide a 'cost' to configurations if using cost" " with BayesianOptimization." ) - if budget_info.max_cost_budget is None: + if max_cost_total_info.max_cost_total is None: raise ValueError("Cost budget must be set if using cost") - cost_percent = budget_info.used_cost_budget / budget_info.max_cost_budget + cost_percent = ( + max_cost_total_info.used_cost_budget / max_cost_total_info.max_cost_total + ) # If we should use the prior, weight the acquisition function by # the probability of it being sampled from the prior. diff --git a/neps/optimizers/grid_search/optimizer.py b/neps/optimizers/grid_search/optimizer.py index 1da004e7..7bd61286 100644 --- a/neps/optimizers/grid_search/optimizer.py +++ b/neps/optimizers/grid_search/optimizer.py @@ -97,7 +97,7 @@ def __init__(self, pipeline_space: SearchSpace, seed: int | None = None): @override def ask( - self, trials: Mapping[str, Trial], budget_info: BudgetInfo | None + self, trials: Mapping[str, Trial], max_cost_total_info: BudgetInfo | None ) -> SampledConfig: _num_previous_configs = len(trials) if _num_previous_configs > len(self.configs_list) - 1: diff --git a/neps/optimizers/initial_design.py b/neps/optimizers/initial_design.py index ee64fd2a..139ff82d 100644 --- a/neps/optimizers/initial_design.py +++ b/neps/optimizers/initial_design.py @@ -102,7 +102,7 @@ def make_initial_design( # noqa: PLR0912, C901 if sample_default_first: # TODO: No way to pass a seed to the sampler default = { - name: hp.default if hp.default is not None else hp.sample_value() + name: hp.prior if hp.prior is not None else hp.sample_value() for name, hp in space.hyperparameters.items() } configs.append({**default, **fids()}) diff --git a/neps/optimizers/multi_fidelity/hyperband.py b/neps/optimizers/multi_fidelity/hyperband.py index 10861b02..5a469a5c 100644 --- a/neps/optimizers/multi_fidelity/hyperband.py +++ b/neps/optimizers/multi_fidelity/hyperband.py @@ -47,13 +47,13 @@ def __init__( self, *, pipeline_space: SearchSpace, - budget: int, + max_cost_total: int, eta: int = 3, initial_design_type: Literal["max_budget", "unique_configs"] = "max_budget", use_priors: bool = False, sampling_policy: Any = RandomUniformPolicy, promotion_policy: Any = SyncPromotionPolicy, - loss_value_on_error: None | float = None, + objective_to_minimize_value_on_error: None | float = None, cost_value_on_error: None | float = None, ignore_errors: bool = False, prior_confidence: Literal["low", "medium", "high"] | None = None, @@ -63,14 +63,14 @@ def __init__( ): args = { "pipeline_space": pipeline_space, - "budget": budget, + "max_cost_total": max_cost_total, "eta": eta, "early_stopping_rate": self.early_stopping_rate, # HB subsumes this from SH "initial_design_type": initial_design_type, "use_priors": use_priors, "sampling_policy": sampling_policy, "promotion_policy": promotion_policy, - "loss_value_on_error": loss_value_on_error, + "objective_to_minimize_value_on_error": objective_to_minimize_value_on_error, "cost_value_on_error": cost_value_on_error, "ignore_errors": ignore_errors, "prior_confidence": prior_confidence, @@ -125,7 +125,7 @@ def _handle_promotions(self) -> None: def ask( self, trials: Mapping[str, Trial], - budget_info: BudgetInfo | None, + max_cost_total_info: BudgetInfo | None, ) -> SampledConfig: completed: dict[str, ConfigResult] = { trial_id: trial.into_config_result(self.pipeline_space.from_dict) @@ -273,12 +273,12 @@ def __init__( self, *, pipeline_space: SearchSpace, - budget: int, + max_cost_total: int, eta: int = 3, initial_design_type: Literal["max_budget", "unique_configs"] = "max_budget", sampling_policy: Any = FixedPriorPolicy, promotion_policy: Any = SyncPromotionPolicy, - loss_value_on_error: None | float = None, + objective_to_minimize_value_on_error: None | float = None, cost_value_on_error: None | float = None, ignore_errors: bool = False, prior_confidence: Literal["low", "medium", "high"] = "medium", @@ -288,13 +288,13 @@ def __init__( ): super().__init__( pipeline_space=pipeline_space, - budget=budget, + max_cost_total=max_cost_total, eta=eta, initial_design_type=initial_design_type, use_priors=self.use_priors, # key change to the base HB class sampling_policy=sampling_policy, promotion_policy=promotion_policy, - loss_value_on_error=loss_value_on_error, + objective_to_minimize_value_on_error=objective_to_minimize_value_on_error, cost_value_on_error=cost_value_on_error, ignore_errors=ignore_errors, prior_confidence=prior_confidence, @@ -311,12 +311,12 @@ def __init__( self, *, pipeline_space: SearchSpace, - budget: int, + max_cost_total: int, eta: int = 3, initial_design_type: Literal["max_budget", "unique_configs"] = "max_budget", sampling_policy: Any = EnsemblePolicy, promotion_policy: Any = SyncPromotionPolicy, - loss_value_on_error: None | float = None, + objective_to_minimize_value_on_error: None | float = None, cost_value_on_error: None | float = None, ignore_errors: bool = False, prior_confidence: Literal["low", "medium", "high"] = "medium", @@ -326,12 +326,12 @@ def __init__( ): super().__init__( pipeline_space=pipeline_space, - budget=budget, + max_cost_total=max_cost_total, eta=eta, initial_design_type=initial_design_type, sampling_policy=sampling_policy, promotion_policy=promotion_policy, - loss_value_on_error=loss_value_on_error, + objective_to_minimize_value_on_error=objective_to_minimize_value_on_error, cost_value_on_error=cost_value_on_error, ignore_errors=ignore_errors, prior_confidence=prior_confidence, @@ -361,13 +361,13 @@ def __init__( self, *, pipeline_space: SearchSpace, - budget: int, + max_cost_total: int, eta: int = 3, initial_design_type: Literal["max_budget", "unique_configs"] = "max_budget", use_priors: bool = False, sampling_policy: Any = RandomUniformPolicy, promotion_policy: Any = AsyncPromotionPolicy, - loss_value_on_error: None | float = None, + objective_to_minimize_value_on_error: None | float = None, cost_value_on_error: None | float = None, ignore_errors: bool = False, prior_confidence: Literal["low", "medium", "high"] | None = None, @@ -377,13 +377,13 @@ def __init__( ): args = { "pipeline_space": pipeline_space, - "budget": budget, + "max_cost_total": max_cost_total, "eta": eta, "initial_design_type": initial_design_type, "use_priors": use_priors, "sampling_policy": sampling_policy, "promotion_policy": promotion_policy, - "loss_value_on_error": loss_value_on_error, + "objective_to_minimize_value_on_error": objective_to_minimize_value_on_error, "cost_value_on_error": cost_value_on_error, "ignore_errors": ignore_errors, "prior_confidence": prior_confidence, @@ -456,12 +456,12 @@ def __init__( self, *, pipeline_space: SearchSpace, - budget: int, + max_cost_total: int, eta: int = 3, initial_design_type: Literal["max_budget", "unique_configs"] = "max_budget", sampling_policy: Any = FixedPriorPolicy, promotion_policy: Any = AsyncPromotionPolicy, - loss_value_on_error: None | float = None, + objective_to_minimize_value_on_error: None | float = None, cost_value_on_error: None | float = None, ignore_errors: bool = False, prior_confidence: Literal["low", "medium", "high"] = "medium", @@ -471,13 +471,13 @@ def __init__( ): super().__init__( pipeline_space=pipeline_space, - budget=budget, + max_cost_total=max_cost_total, eta=eta, initial_design_type=initial_design_type, use_priors=self.use_priors, # key change to the base Async HB class sampling_policy=sampling_policy, promotion_policy=promotion_policy, - loss_value_on_error=loss_value_on_error, + objective_to_minimize_value_on_error=objective_to_minimize_value_on_error, cost_value_on_error=cost_value_on_error, ignore_errors=ignore_errors, prior_confidence=prior_confidence, @@ -495,13 +495,13 @@ def __init__( self, *, pipeline_space: SearchSpace, - budget: int, + max_cost_total: int, eta: int = 3, initial_design_type: Literal["max_budget", "unique_configs"] = "max_budget", use_priors: bool = False, sampling_policy: Any = RandomUniformPolicy, promotion_policy: Any = AsyncPromotionPolicy, - loss_value_on_error: None | float = None, + objective_to_minimize_value_on_error: None | float = None, cost_value_on_error: None | float = None, ignore_errors: bool = False, prior_confidence: Literal["low", "medium", "high"] | None = None, @@ -520,13 +520,13 @@ def __init__( ): hb_args = { "pipeline_space": pipeline_space, - "budget": budget, + "max_cost_total": max_cost_total, "eta": eta, "initial_design_type": initial_design_type, "use_priors": use_priors, "sampling_policy": sampling_policy, "promotion_policy": promotion_policy, - "loss_value_on_error": loss_value_on_error, + "objective_to_minimize_value_on_error": objective_to_minimize_value_on_error, "cost_value_on_error": cost_value_on_error, "ignore_errors": ignore_errors, "prior_confidence": prior_confidence, diff --git a/neps/optimizers/multi_fidelity/ifbo.py b/neps/optimizers/multi_fidelity/ifbo.py index 8f873b2c..4f09faf9 100755 --- a/neps/optimizers/multi_fidelity/ifbo.py +++ b/neps/optimizers/multi_fidelity/ifbo.py @@ -73,9 +73,9 @@ def _adjust_pipeline_space_to_match_stepsize( lower=new_lower, upper=fidelity.upper, log=fidelity.log, - default=fidelity.default, + prior=fidelity.prior, is_fidelity=True, - default_confidence=fidelity.default_confidence_choice, + prior_confidence=fidelity.prior_confidence_choice, ) return ( SearchSpace(**{**pipeline_space.hyperparameters, fidelity_name: new_fid}), @@ -98,8 +98,8 @@ def __init__( initial_design_size: int | Literal["ndim"] = "ndim", n_acquisition_new_configs: int = 1_000, device: torch.device | None = None, - budget: int | float | None = None, # TODO: Remove - loss_value_on_error: float | None = None, # TODO: Remove + max_cost_total: int | float | None = None, # TODO: Remove + objective_to_minimize_value_on_error: float | None = None, # TODO: Remove cost_value_on_error: float | None = None, # TODO: Remove ignore_errors: bool = False, # TODO: Remove ): @@ -171,7 +171,7 @@ def __init__( def ask( self, trials: Mapping[str, Trial], - budget_info: BudgetInfo | None = None, + max_cost_total_info: BudgetInfo | None = None, ) -> SampledConfig: ids = [int(config_id.split("_", maxsplit=1)[0]) for config_id in trials] new_id = max(ids) + 1 if len(ids) > 0 else 0 @@ -222,7 +222,8 @@ def ask( not_pending_X = X # NOTE: Can't really abstract this, requires knowledge that: - # 1. The encoding is such that the loss is 1 - loss + # 1. The encoding is such that the objective_to_minimize is 1 - + # objective_to_minimize # 2. The budget is the second column # 3. The budget is encoded between 1/max_fid and 1 rng = np.random.RandomState(len(trials)) diff --git a/neps/optimizers/multi_fidelity/sampling_policy.py b/neps/optimizers/multi_fidelity/sampling_policy.py index 7e883e69..dd510c1c 100644 --- a/neps/optimizers/multi_fidelity/sampling_policy.py +++ b/neps/optimizers/multi_fidelity/sampling_policy.py @@ -214,7 +214,7 @@ def sample( # noqa: PLR0912, C901, PLR0915 user_priors = False if inc is None: - inc = self.pipeline_space.from_dict(self.pipeline_space.default_config) + inc = self.pipeline_space.from_dict(self.pipeline_space.prior_config) logger.warning( "No incumbent config found, using default as the incumbent." ) diff --git a/neps/optimizers/multi_fidelity/successive_halving.py b/neps/optimizers/multi_fidelity/successive_halving.py index 3d27b061..74cdbab5 100644 --- a/neps/optimizers/multi_fidelity/successive_halving.py +++ b/neps/optimizers/multi_fidelity/successive_halving.py @@ -38,7 +38,7 @@ CUSTOM_FLOAT_CONFIDENCE_SCORES = dict(Float.DEFAULT_CONFIDENCE_SCORES) CUSTOM_FLOAT_CONFIDENCE_SCORES.update({"ultra": 0.05}) -CUSTOM_CATEGORICAL_CONFIDENCE_SCORES = dict(Categorical.DEFAULT_CONFIDENCE_SCORES) +CUSTOM_CATEGORICAL_CONFIDENCE_SCORES = dict(Categorical.PRIOR_CONFIDENCE_SCORES) CUSTOM_CATEGORICAL_CONFIDENCE_SCORES.update({"ultra": 8}) @@ -49,14 +49,14 @@ def __init__( self, *, pipeline_space: SearchSpace, - budget: int | None = None, + max_cost_total: int | None = None, eta: int = 3, early_stopping_rate: int = 0, initial_design_type: Literal["max_budget", "unique_configs"] = "max_budget", use_priors: bool = False, sampling_policy: Any = RandomUniformPolicy, promotion_policy: Any = SyncPromotionPolicy, - loss_value_on_error: None | float = None, + objective_to_minimize_value_on_error: None | float = None, cost_value_on_error: None | float = None, ignore_errors: bool = False, prior_confidence: Literal["low", "medium", "high"] | None = None, @@ -68,7 +68,7 @@ def __init__( Args: pipeline_space: Space in which to search - budget: Maximum budget + max_cost_total: Maximum budget eta: The reduction factor used by SH early_stopping_rate: Determines the number of rungs in an SH bracket Choosing 0 creates maximal rungs given the fidelity bounds @@ -78,12 +78,12 @@ def __init__( Samples generated from a Gaussian centered around the default value sampling_policy: The type of sampling procedure to use promotion_policy: The type of promotion procedure to use - loss_value_on_error: Setting this and cost_value_on_error to any float will - supress any error during bayesian optimization and will use given loss - value instead. default: None - cost_value_on_error: Setting this and loss_value_on_error to any float will - supress any error during bayesian optimization and will use given cost - value instead. default: None + objective_to_minimize_value_on_error: Setting this and cost_value_on_error to + any float will supress any error during bayesian optimization and will + use given objective_to_minimize value instead. default: None + cost_value_on_error: Setting this and objective_to_minimize_value_on_error to + any float will supress any error during bayesian optimization and will + use given cost value instead. default: None prior_confidence: The range of confidence to have on the prior The higher the confidence, the smaller is the standard deviation of the prior distribution centered around the default @@ -94,8 +94,8 @@ def __init__( """ super().__init__( pipeline_space=pipeline_space, - budget=budget, - loss_value_on_error=loss_value_on_error, + max_cost_total=max_cost_total, + objective_to_minimize_value_on_error=objective_to_minimize_value_on_error, cost_value_on_error=cost_value_on_error, ignore_errors=ignore_errors, ) @@ -224,7 +224,7 @@ def _load_previous_observations( ) -> None: for config_id, config_val in previous_results.items(): _config, _rung = self._get_config_id_split(config_id) - perf = self.get_loss(config_val.result) + perf = self.get_objective_to_minimize(config_val.result) if int(_config) in self.observed_configs.index: # config already recorded in dataframe rung_recorded = self.observed_configs.at[int(_config), "rung"] @@ -320,7 +320,7 @@ def _fit_models(self) -> None: def ask( self, trials: Mapping[str, Trial], - budget_info: BudgetInfo | None, + max_cost_total_info: BudgetInfo | None, ) -> SampledConfig: """This is basically the fit method.""" completed: dict[str, ConfigResult] = { @@ -437,7 +437,7 @@ def get_config_and_ids(self) -> tuple[RawConfig, str, str | None]: rung_id = self.max_rung logger.info("Next config will be evaluated at target fidelity.") logger.info("Sampling the default configuration...") - config = self.pipeline_space.from_dict(self.pipeline_space.default_config) + config = self.pipeline_space.from_dict(self.pipeline_space.prior_config) elif rng.random() < self.random_interleave_prob: config = sample_one_old( self.pipeline_space, @@ -476,7 +476,7 @@ def _enhance_priors(self, confidence_score: dict[str, float] | None = None) -> N confidence = CUSTOM_FLOAT_CONFIDENCE_SCORES[self.prior_confidence] else: confidence = confidence_score["numeric"] - self.pipeline_space[k].default_confidence_score = confidence + self.pipeline_space[k].prior_confidence_score = confidence elif isinstance(v, Categorical): if confidence_score is None: confidence = CUSTOM_CATEGORICAL_CONFIDENCE_SCORES[ @@ -484,18 +484,18 @@ def _enhance_priors(self, confidence_score: dict[str, float] | None = None) -> N ] else: confidence = confidence_score["categorical"] - self.pipeline_space[k].default_confidence_score = confidence + self.pipeline_space[k].prior_confidence_score = confidence class SuccessiveHalving(SuccessiveHalvingBase): def _calc_budget_used_in_bracket(self, config_history: list[int]) -> int: - budget = 0 + max_cost_total = 0 for rung in self.config_map: count = sum(config_history == rung) # `range(min_rung, rung+1)` counts the black-box cost of promotions since # SH budgets assume each promotion involves evaluation from scratch - budget += count * sum(np.arange(self.min_rung, rung + 1)) - return budget + max_cost_total += count * sum(np.arange(self.min_rung, rung + 1)) + return max_cost_total def clear_old_brackets(self) -> None: """Enforces reset at each new bracket. @@ -559,13 +559,13 @@ def __init__( self, *, pipeline_space: SearchSpace, - budget: int, + max_cost_total: int, eta: int = 3, early_stopping_rate: int = 0, initial_design_type: Literal["max_budget", "unique_configs"] = "max_budget", sampling_policy: Any = FixedPriorPolicy, promotion_policy: Any = SyncPromotionPolicy, - loss_value_on_error: None | float = None, + objective_to_minimize_value_on_error: None | float = None, cost_value_on_error: None | float = None, ignore_errors: bool = False, prior_confidence: Literal["low", "medium", "high"] = "medium", # medium = 0.25 @@ -575,14 +575,14 @@ def __init__( ): super().__init__( pipeline_space=pipeline_space, - budget=budget, + max_cost_total=max_cost_total, eta=eta, early_stopping_rate=early_stopping_rate, initial_design_type=initial_design_type, use_priors=self.use_priors, sampling_policy=sampling_policy, promotion_policy=promotion_policy, - loss_value_on_error=loss_value_on_error, + objective_to_minimize_value_on_error=objective_to_minimize_value_on_error, cost_value_on_error=cost_value_on_error, ignore_errors=ignore_errors, prior_confidence=prior_confidence, @@ -599,14 +599,14 @@ def __init__( self, *, pipeline_space: SearchSpace, - budget: int, + max_cost_total: int, eta: int = 3, early_stopping_rate: int = 0, initial_design_type: Literal["max_budget", "unique_configs"] = "max_budget", use_priors: bool = False, sampling_policy: Any = RandomUniformPolicy, promotion_policy: Any = AsyncPromotionPolicy, # key difference from SH - loss_value_on_error: None | float = None, + objective_to_minimize_value_on_error: None | float = None, cost_value_on_error: None | float = None, ignore_errors: bool = False, prior_confidence: Literal["low", "medium", "high"] | None = None, @@ -616,14 +616,14 @@ def __init__( ): super().__init__( pipeline_space=pipeline_space, - budget=budget, + max_cost_total=max_cost_total, eta=eta, early_stopping_rate=early_stopping_rate, initial_design_type=initial_design_type, use_priors=use_priors, sampling_policy=sampling_policy, promotion_policy=promotion_policy, - loss_value_on_error=loss_value_on_error, + objective_to_minimize_value_on_error=objective_to_minimize_value_on_error, cost_value_on_error=cost_value_on_error, ignore_errors=ignore_errors, prior_confidence=prior_confidence, @@ -642,13 +642,13 @@ def __init__( self, *, pipeline_space: SearchSpace, - budget: int, + max_cost_total: int, eta: int = 3, early_stopping_rate: int = 0, initial_design_type: Literal["max_budget", "unique_configs"] = "max_budget", sampling_policy: Any = FixedPriorPolicy, promotion_policy: Any = AsyncPromotionPolicy, # key difference from SH - loss_value_on_error: None | float = None, + objective_to_minimize_value_on_error: None | float = None, cost_value_on_error: None | float = None, ignore_errors: bool = False, prior_confidence: Literal["low", "medium", "high"] = "medium", @@ -658,14 +658,14 @@ def __init__( ): super().__init__( pipeline_space=pipeline_space, - budget=budget, + max_cost_total=max_cost_total, eta=eta, early_stopping_rate=early_stopping_rate, initial_design_type=initial_design_type, use_priors=self.use_priors, sampling_policy=sampling_policy, promotion_policy=promotion_policy, - loss_value_on_error=loss_value_on_error, + objective_to_minimize_value_on_error=objective_to_minimize_value_on_error, cost_value_on_error=cost_value_on_error, ignore_errors=ignore_errors, prior_confidence=prior_confidence, diff --git a/neps/optimizers/multi_fidelity/utils.py b/neps/optimizers/multi_fidelity/utils.py index 059e2ca9..bbc6557f 100644 --- a/neps/optimizers/multi_fidelity/utils.py +++ b/neps/optimizers/multi_fidelity/utils.py @@ -188,7 +188,7 @@ def extract_learning_curve( # For the first epoch we have no learning curve available if budget_id == 0: return [] - # reduce budget_id to discount the current validation loss + # reduce budget_id to discount the current validation objective_to_minimize # both during training and prediction phase budget_id = max(0, budget_id - 1) if self.lc_col_name in self.df.columns: diff --git a/neps/optimizers/multi_fidelity_prior/async_priorband.py b/neps/optimizers/multi_fidelity_prior/async_priorband.py index 7adcdd9a..2b65b97a 100644 --- a/neps/optimizers/multi_fidelity_prior/async_priorband.py +++ b/neps/optimizers/multi_fidelity_prior/async_priorband.py @@ -34,13 +34,13 @@ def __init__( self, *, pipeline_space: SearchSpace, - budget: int, + max_cost_total: int, eta: int = 3, early_stopping_rate: int = 0, initial_design_type: Literal["max_budget", "unique_configs"] = "max_budget", sampling_policy: Any = EnsemblePolicy, # key difference to ASHA promotion_policy: Any = AsyncPromotionPolicy, # key difference from SH - loss_value_on_error: None | float = None, + objective_to_minimize_value_on_error: None | float = None, cost_value_on_error: None | float = None, ignore_errors: bool = False, prior_confidence: Literal["low", "medium", "high"] = "medium", @@ -70,13 +70,13 @@ def __init__( ): super().__init__( pipeline_space=pipeline_space, - budget=budget, + max_cost_total=max_cost_total, eta=eta, early_stopping_rate=early_stopping_rate, initial_design_type=initial_design_type, sampling_policy=sampling_policy, promotion_policy=promotion_policy, - loss_value_on_error=loss_value_on_error, + objective_to_minimize_value_on_error=objective_to_minimize_value_on_error, cost_value_on_error=cost_value_on_error, ignore_errors=ignore_errors, prior_confidence=prior_confidence, @@ -143,12 +143,12 @@ def __init__( self, *, pipeline_space: SearchSpace, - budget: int, + max_cost_total: int, eta: int = 3, initial_design_type: Literal["max_budget", "unique_configs"] = "max_budget", sampling_policy: Any = EnsemblePolicy, # key difference to ASHA promotion_policy: Any = AsyncPromotionPolicy, # key difference from PB - loss_value_on_error: None | float = None, + objective_to_minimize_value_on_error: None | float = None, cost_value_on_error: None | float = None, ignore_errors: bool = False, prior_confidence: Literal["low", "medium", "high"] = "medium", @@ -179,13 +179,13 @@ def __init__( # collecting arguments required by ASHA args: dict[str, Any] = { "pipeline_space": pipeline_space, - "budget": budget, + "max_cost_total": max_cost_total, "eta": eta, "early_stopping_rate": self.early_stopping_rate, "initial_design_type": initial_design_type, "sampling_policy": sampling_policy, "promotion_policy": promotion_policy, - "loss_value_on_error": loss_value_on_error, + "objective_to_minimize_value_on_error": objective_to_minimize_value_on_error, "cost_value_on_error": cost_value_on_error, "ignore_errors": ignore_errors, "prior_confidence": prior_confidence, @@ -238,7 +238,7 @@ def _update_sh_bracket_state(self) -> None: def ask( self, trials: Mapping[str, Trial], - budget_info: BudgetInfo | None, + max_cost_total_info: BudgetInfo | None, ) -> SampledConfig: """This is basically the fit method.""" completed: dict[str, ConfigResult] = { diff --git a/neps/optimizers/multi_fidelity_prior/priorband.py b/neps/optimizers/multi_fidelity_prior/priorband.py index ef9c6002..e5ffbd29 100644 --- a/neps/optimizers/multi_fidelity_prior/priorband.py +++ b/neps/optimizers/multi_fidelity_prior/priorband.py @@ -91,7 +91,7 @@ def find_incumbent(self, rung: int | None = None) -> SearchSpace: else: # THIS block should not ever execute, but for runtime anomalies, if no # incumbent can be extracted, the prior is treated as the incumbent - inc = self.pipeline_space.from_dict(self.pipeline_space.default_config) + inc = self.pipeline_space.from_dict(self.pipeline_space.prior_config) logger.warning( "Treating the prior as the incumbent. " "Please check if this should not happen." @@ -259,7 +259,7 @@ def _prior_to_incumbent_ratio_dynamic(self, rung: int) -> tuple[float, float]: # requires at least eta completed configurations to begin computing scores if len(self.rung_histories[rung]["config"]) >= self.eta: # retrieve the prior - prior = self.pipeline_space.from_dict(self.pipeline_space.default_config) + prior = self.pipeline_space.from_dict(self.pipeline_space.prior_config) # retrieve the global incumbent inc = self.find_incumbent() # subsetting the top 1/eta configs from the rung @@ -304,12 +304,12 @@ def __init__( self, *, pipeline_space: SearchSpace, - budget: int, + max_cost_total: int, eta: int = 3, initial_design_type: Literal["max_budget", "unique_configs"] = "max_budget", sampling_policy: Any = EnsemblePolicy, promotion_policy: Any = SyncPromotionPolicy, - loss_value_on_error: None | float = None, + objective_to_minimize_value_on_error: None | float = None, cost_value_on_error: None | float = None, ignore_errors: bool = False, prior_confidence: Literal["low", "medium", "high"] = "medium", @@ -337,12 +337,12 @@ def __init__( ): super().__init__( pipeline_space=pipeline_space, - budget=budget, + max_cost_total=max_cost_total, eta=eta, initial_design_type=initial_design_type, sampling_policy=sampling_policy, promotion_policy=promotion_policy, - loss_value_on_error=loss_value_on_error, + objective_to_minimize_value_on_error=objective_to_minimize_value_on_error, cost_value_on_error=cost_value_on_error, ignore_errors=ignore_errors, prior_confidence=prior_confidence, diff --git a/neps/optimizers/random_search/optimizer.py b/neps/optimizers/random_search/optimizer.py index 8bcc8178..285fd964 100644 --- a/neps/optimizers/random_search/optimizer.py +++ b/neps/optimizers/random_search/optimizer.py @@ -55,7 +55,7 @@ def __init__( def ask( self, trials: Mapping[str, Trial], - budget_info: BudgetInfo | None, + max_cost_total_info: BudgetInfo | None, ) -> SampledConfig: n_trials = len(trials) config = self.sampler.sample_one(to=self.encoder.domains) diff --git a/neps/plot/__main__.py b/neps/plot/__main__.py index 04aaffd2..e94e6559 100644 --- a/neps/plot/__main__.py +++ b/neps/plot/__main__.py @@ -12,7 +12,7 @@ Optional arguments: -h, --help Show this help message and exit --scientific_mode If true, plot from a tree-structured root_directory: benchmark={}/algorithm={}/seed={} - --key_to_extract The metric to be used on the x-axis (if active, make sure run_pipeline returns the metric in the info_dict) + --key_to_extract The metric to be used on the x-axis (if active, make sure evaluate_pipeline returns the metric in the info_dict) --benchmarks List of benchmarks to plot --algorithms List of algorithms to plot --consider_continuations If true, toggle calculation of continuation costs @@ -57,7 +57,7 @@ parser.add_argument( "--key_to_extract", help="The metric to be used on the x-axis (if " - "active, make sure run_pipeline returns " + "active, make sure evaluate_pipeline returns " "the metric in the info_dict)") parser.add_argument( "--benchmarks", diff --git a/neps/plot/plot.py b/neps/plot/plot.py index 020242a4..c36f1842 100644 --- a/neps/plot/plot.py +++ b/neps/plot/plot.py @@ -36,7 +36,7 @@ def plot( # noqa: C901, PLR0913 scientific_mode: If true, plot from a tree-structured root_directory: benchmark={}/algorithm={}/seed={} key_to_extract: The metric to be used on the x-axis - (if active, make sure run_pipeline returns the metric in the info_dict) + (if active, make sure evaluate_pipeline returns the metric in the info_dict) benchmarks: List of benchmarks to plot algorithms: List of algorithms to plot consider_continuations: If true, toggle calculation of continuation costs diff --git a/neps/plot/plot3D.py b/neps/plot/plot3D.py index 37618a48..e0d83598 100644 --- a/neps/plot/plot3D.py +++ b/neps/plot/plot3D.py @@ -23,7 +23,7 @@ class Plotter3D: """Plot a 3d landscape of learning curves for a given run.""" - loss_key: str = "Loss" + objective_to_minimize_key: str = "Objective to minimize" fidelity_key: str = "epochs" run_path: str | Path | None = None scatter: bool = True @@ -49,7 +49,7 @@ def __post_init__(self) -> None: ) # Assigned at prep_df stage - self.loss_range: tuple[float, float] | None = None + self.objective_to_minimize_range: tuple[float, float] | None = None self.epochs_range: tuple[float, float] | None = None @staticmethod @@ -66,7 +66,7 @@ def get_y(df: pd.DataFrame) -> np.ndarray: @staticmethod def get_z(df: pd.DataFrame) -> np.ndarray: """Get the z-axis values for the plot.""" - return df["result.loss"].to_numpy() + return df["result.objective_to_minimize"].to_numpy() @staticmethod def get_color(df: pd.DataFrame) -> np.ndarray: @@ -78,7 +78,10 @@ def prep_df(self, df: pd.DataFrame | None = None) -> pd.DataFrame: df = self.df if df is None else df _fid_key = f"config.{self.fidelity_key}" - self.loss_range = (df["result.loss"].min(), df["result.loss"].max()) # type: ignore + self.objective_to_minimize_range = ( + df["result.objective_to_minimize"].min(), + df["result.objective_to_minimize"].max(), + ) # type: ignore self.epochs_range = (df[_fid_key].min(), df[_fid_key].max()) # type: ignore split_values = np.array([[*index.split("_")] for index in df.index]) @@ -189,23 +192,26 @@ def plot3D( # noqa: N802, PLR0915 # Draw lines ax.add_collection(lc) - assert self.loss_range is not None + assert self.objective_to_minimize_range is not None assert self.epochs_range is not None ax3D.axes.set_xlim3d(left=self.epochs_range[0], right=self.epochs_range[1]) # type: ignore ax3D.axes.set_ylim3d(bottom=0, top=data_groups.ngroups) # type: ignore - ax3D.axes.set_zlim3d(bottom=self.loss_range[0], top=self.loss_range[1]) # type: ignore + ax3D.axes.set_zlim3d( + bottom=self.objective_to_minimize_range[0], + top=self.objective_to_minimize_range[1], + ) # type: ignore ax3D.set_xlabel("Epochs") ax3D.set_ylabel("Iteration sampled") - ax3D.set_zlabel(f"{self.loss_key}") # type: ignore + ax3D.set_zlabel(f"{self.objective_to_minimize_key}") # type: ignore # set view angle ax3D.view_init(elev=self.view_angle[0], azim=self.view_angle[1]) # type: ignore ax.autoscale_view() ax.set_xlabel(self.fidelity_key) - ax.set_ylabel(f"{self.loss_key}") + ax.set_ylabel(f"{self.objective_to_minimize_key}") ax.set_facecolor(self.bck_color_2d) fig.suptitle("ifBO run") diff --git a/neps/plot/read_results.py b/neps/plot/read_results.py index 06ff3f1c..24b8a288 100644 --- a/neps/plot/read_results.py +++ b/neps/plot/read_results.py @@ -62,9 +62,9 @@ def get_cost(idx: str) -> float: else: config_cost = config_result.metadata["time_end"] - global_start - # TODO(eddiebergman): Assumes it never crashed and there's a loss available, - # not fixing now but it should be addressed - losses.append(config_result.result["loss"]) # type: ignore + # TODO(eddiebergman): Assumes it never crashed and there's a + # objective_to_minimize available,not fixing now but it should be addressed + losses.append(config_result.result["objective_to_minimize"]) # type: ignore costs.append(config_cost) return list(np.minimum.accumulate(losses)), costs, max_cost diff --git a/neps/plot/tensorboard_eval.py b/neps/plot/tensorboard_eval.py index 380ad6b4..9022a825 100644 --- a/neps/plot/tensorboard_eval.py +++ b/neps/plot/tensorboard_eval.py @@ -35,7 +35,7 @@ class SummaryWriter_(SummaryWriter): # noqa: N801 - Ensures all logs are stored in the same 'tfevent' directory for better organization. - Updates metric keys to have a consistent 'Summary/' prefix for clarity. - - Improves the display of 'Loss' or 'Accuracy' on the Summary file. + - Improves the display of 'objective_to_minimize' or 'Accuracy' on the Summary file. Methods: - add_hparams: Overrides the base method to log hyperparameters and @@ -74,7 +74,7 @@ class tblogger: # noqa: N801 disable_logging: ClassVar[bool] = False - loss: ClassVar[float | None] = None + objective_to_minimize: ClassVar[float | None] = None current_epoch: ClassVar[int | None] = None write_incumbent: ClassVar[bool | None] = None @@ -377,20 +377,20 @@ def _write_hparam_config() -> None: TensorBoard writer is initialized at the correct directory. It also depends on the following global variables: - - tblogger.loss (float) + - tblogger.objective_to_minimize (float) - tblogger.config_writer (SummaryWriter_) - tblogger.config (dict) - tblogger.current_epoch (int) The function will log hyperparameter configurations along - with a metric value (either accuracy or loss) to TensorBoard + with a metric value (either accuracy or objective_to_minimize) to TensorBoard based on the given configurations. """ if not tblogger._is_initialized(): tblogger._initialize_writers() - str_name = "Loss" - str_value = tblogger.loss + str_name = "Objective to minimize" + str_value = tblogger.objective_to_minimize values = {str_name: str_value} # Just an extra safety measure @@ -411,7 +411,8 @@ def _write_hparam_config() -> None: @staticmethod def _tracking_incumbent_api() -> None: - """Track the incumbent (best) loss and log it in the TensorBoard summary. + """Track the incumbent (best) objective_to_minimize and log it in the TensorBoard + summary. Note: The function relies on the following global variables: @@ -424,7 +425,7 @@ def _tracking_incumbent_api() -> None: summary_dict = get_summary_dict(tblogger.optimizer_dir, add_details=True) incum_tracker = summary_dict["num_evaluated_configs"] - incum_val = summary_dict["best_loss"] + incum_val = summary_dict["best_objective_to_minimize"] if tblogger.summary_writer is None and tblogger.optimizer_dir is not None: tblogger.summary_writer = SummaryWriter_(tblogger.optimizer_dir / "summary") @@ -478,7 +479,7 @@ def get_status() -> bool: @staticmethod def log( - loss: float, + objective_to_minimize: float, current_epoch: int, *, writer_config_scalar: bool = True, @@ -490,9 +491,9 @@ def log( hyperparameters, and images. Args: - loss: Current loss value. + objective_to_minimize: Current objective_to_minimize value. current_epoch: Current epoch of the experiment (used as the global step). - writer_config_scalar: Displaying the loss or accuracy + writer_config_scalar: Displaying the objective_to_minimize or accuracy curve on tensorboard (default: True) writer_config_hparam: Write hyperparameters logging of the configs. write_summary_incumbent: Set to `True` for a live incumbent trajectory. @@ -502,13 +503,15 @@ def log( return tblogger.current_epoch = current_epoch - tblogger.loss = loss + tblogger.objective_to_minimize = objective_to_minimize tblogger.write_incumbent = write_summary_incumbent tblogger._initiate_internal_configurations() if writer_config_scalar: - tblogger._write_scalar_config(tag="Loss", value=loss) + tblogger._write_scalar_config( + tag="objective_to_minimize", value=objective_to_minimize + ) if writer_config_hparam: tblogger._write_hparam_config() diff --git a/neps/runtime.py b/neps/runtime.py index 71b72f87..7fcdf6a4 100644 --- a/neps/runtime.py +++ b/neps/runtime.py @@ -65,9 +65,9 @@ def get_workers_neps_state() -> NePSState[Path]: if _WORKER_NEPS_STATE is None: raise RuntimeError( "The worker's NePS state has not been set! This should only be called" - " from within a `run_pipeline` context. If you are not running a pipeline" - " and you did not call this function (`get_workers_neps_state`) yourself," - " this is a bug and should be reported to NePS." + " from within a `evaluate_pipeline` context. If you are not running a" + " pipeline and you did not call this function (`get_workers_neps_state`)" + " yourself, this is a bug and should be reported to NePS." ) return _WORKER_NEPS_STATE @@ -82,9 +82,9 @@ def get_in_progress_trial() -> Trial: if _CURRENTLY_RUNNING_TRIAL_IN_PROCESS is None: raise RuntimeError( "The worker's NePS state has not been set! This should only be called" - " from within a `run_pipeline` context. If you are not running a pipeline" - " and you did not call this function (`get_workers_neps_state`) yourself," - " this is a bug and should be reported to NePS." + " from within a `evaluate_pipeline` context. If you are not running a" + " pipeline and you did not call this function (`get_workers_neps_state`)" + " yourself, this is a bug and should be reported to NePS." ) return _CURRENTLY_RUNNING_TRIAL_IN_PROCESS @@ -481,8 +481,8 @@ def run(self) -> None: # noqa: C901, PLR0915 ) logger.debug("Config %s: %s", evaluated_trial.id, evaluated_trial.config) - logger.debug("Loss %s: %s", evaluated_trial.id, report.loss) - logger.debug("Cost %s: %s", evaluated_trial.id, report.loss) + logger.debug("Loss %s: %s", evaluated_trial.id, report.objective_to_minimize) + logger.debug("Cost %s: %s", evaluated_trial.id, report.objective_to_minimize) logger.debug( "Learning Curve %s: %s", evaluated_trial.id, report.learning_curve ) @@ -498,7 +498,7 @@ def _launch_runtime( # noqa: PLR0913 optimization_dir: Path, max_cost_total: float | None, ignore_errors: bool = False, - loss_value_on_error: float | None, + objective_to_minimize_value_on_error: float | None, cost_value_on_error: float | None, continue_until_max_evaluation_completed: bool, overwrite_optimization_dir: bool, @@ -517,9 +517,9 @@ def _launch_runtime( # noqa: PLR0913 directory=optimization_dir, optimizer_info=OptimizerInfo(optimizer_info), optimizer_state=OptimizationState( - budget=( + max_cost_total_info=( BudgetInfo( - max_cost_budget=max_cost_total, + max_cost_total=max_cost_total, used_cost_budget=0, max_evaluations=max_evaluations_total, used_evaluations=0, @@ -536,11 +536,12 @@ def _launch_runtime( # noqa: PLR0913 else OnErrorPossibilities.RAISE_ANY_ERROR ), default_report_values=DefaultReportValues( - loss_value_on_error=loss_value_on_error, + objective_to_minimize_value_on_error=objective_to_minimize_value_on_error, cost_value_on_error=cost_value_on_error, cost_if_not_provided=None, # TODO: User can't specify yet learning_curve_on_error=None, # TODO: User can't specify yet - learning_curve_if_not_provided="loss", # report the loss as single value LC + learning_curve_if_not_provided="objective_to_minimize", # report the + # objective_to_minimize as single value LC ), max_evaluations_total=max_evaluations_total, include_in_progress_evaluations_towards_maximum=( diff --git a/neps/sampling/priors.py b/neps/sampling/priors.py index 550470b8..be43cb01 100644 --- a/neps/sampling/priors.py +++ b/neps/sampling/priors.py @@ -140,14 +140,14 @@ def from_parameters( for name, hp in parameters.items(): domains.append(hp.domain) - default = center_values.get(name, hp.default) + default = center_values.get(name, hp.prior) if default is None: centers.append(None) continue confidence_score = confidence_values.get( name, - _mapping[hp.default_confidence_choice], + _mapping[hp.prior_confidence_choice], ) center = hp.choices.index(default) if isinstance(hp, Categorical) else default centers.append((center, confidence_score)) @@ -283,7 +283,7 @@ def from_space( Args: space: The search space to createa a prior from. Will look - at the `.default` and `.default_confidence` of the parameters + at the `.default` and `.prior_confidence` of the parameters to create a truncated normal. Any parameters that do not have a `.default` will be covered by a uniform distribution. diff --git a/neps/search_spaces/architecture/graph.py b/neps/search_spaces/architecture/graph.py index 34d01195..b7dd5b5e 100644 --- a/neps/search_spaces/architecture/graph.py +++ b/neps/search_spaces/architecture/graph.py @@ -53,7 +53,7 @@ class Graph(torch.nn.Module, nx.DiGraph): >>> graph = getFancySearchSpace() >>> graph.parse() >>> logits = graph(data) - >>> optimizer.min(loss(logits, target)) + >>> optimizer.min(objective_to_minimize(logits, target)) To update the pytorch module representation (e.g. after removing or adding some new edges), you have to unparse. Beware that this is not fast, so it should diff --git a/neps/search_spaces/architecture/graph_grammar.py b/neps/search_spaces/architecture/graph_grammar.py index 035a686e..e8ce9e90 100644 --- a/neps/search_spaces/architecture/graph_grammar.py +++ b/neps/search_spaces/architecture/graph_grammar.py @@ -43,7 +43,7 @@ class GraphParameter( # noqa: D101 # Essentially on the outside, we need to ensure we don't pass ih the graph object # itself DEFAULT_CONFIDENCE_SCORES: ClassVar[Mapping[str, float]] = {"not_in_use": 1.0} - default_confidence_choice = "not_in_use" + prior_confidence_choice = "not_in_use" has_prior: bool input_kwargs: dict[str, Any] @@ -186,7 +186,7 @@ def __init__( # noqa: D107, PLR0913 scope=scope, **kwargs, ) - GraphParameter.__init__(self, value=None, default=None, is_fidelity=False) + GraphParameter.__init__(self, value=None, prior=None, is_fidelity=False) self.string_tree: str = "" self._function_id: str = "" diff --git a/neps/search_spaces/hyperparameters/categorical.py b/neps/search_spaces/hyperparameters/categorical.py index 8e24690f..aa407d35 100644 --- a/neps/search_spaces/hyperparameters/categorical.py +++ b/neps/search_spaces/hyperparameters/categorical.py @@ -42,7 +42,7 @@ class Categorical(ParameterWithPrior[CategoricalTypes, CategoricalTypes]): for more details on the methods available for this class. """ - DEFAULT_CONFIDENCE_SCORES: ClassVar[Mapping[str, Any]] = { + PRIOR_CONFIDENCE_SCORES: ClassVar[Mapping[str, Any]] = { "low": 2, "medium": 4, "high": 6, @@ -52,23 +52,23 @@ def __init__( self, choices: Iterable[float | int | str], *, - default: float | int | str | None = None, - default_confidence: Literal["low", "medium", "high"] = "low", + prior: float | int | str | None = None, + prior_confidence: Literal["low", "medium", "high"] = "low", ): """Create a new `Categorical`. Args: choices: choices for the hyperparameter. - default: default value for the hyperparameter, must be in `choices=` + prior: prior value for the hyperparameter, must be in `choices=` if provided. - default_confidence: confidence score for the default value, used when + prior_confidence: confidence score for the prior value, used when condsider prior based optimization. """ choices = list(choices) if len(choices) <= 1: raise ValueError("Categorical choices must have more than one value.") - super().__init__(value=None, is_fidelity=False, default=default) + super().__init__(value=None, is_fidelity=False, prior=prior) for choice in choices: if not isinstance(choice, float | int | str): @@ -79,9 +79,9 @@ def __init__( if not all_unique(choices): raise ValueError(f"Choices must be unique but got duplicates.\n{choices}") - if default is not None and default not in choices: + if prior is not None and prior not in choices: raise ValueError( - f"Default value {default} is not in the provided choices {choices}" + f"Default value {prior} is not in the provided choices {choices}" ) self.choices = list(choices) @@ -91,14 +91,14 @@ def __init__( # currently we do a list.index() operation which is O(n). # However for small sized categoricals this is likely faster than # a lookup table. - # For now we can just cache the index of the value and default. + # For now we can just cache the index of the value and prior. self._value_index: int | None = None - self.default_confidence_choice = default_confidence - self.default_confidence_score = self.DEFAULT_CONFIDENCE_SCORES[default_confidence] - self.has_prior = self.default is not None - self._default_index: int | None = ( - self.choices.index(default) if default is not None else None + self.prior_confidence_choice = prior_confidence + self.prior_confidence_score = self.PRIOR_CONFIDENCE_SCORES[prior_confidence] + self.has_prior = self.prior is not None + self._prior_index: int | None = ( + self.choices.index(prior) if prior is not None else None ) self.domain = Domain.indices(len(self.choices)) @@ -106,8 +106,8 @@ def __init__( def clone(self) -> Self: clone = self.__class__( choices=self.choices, - default=self.default, - default_confidence=self.default_confidence_choice, # type: ignore + prior=self.prior, + prior_confidence=self.prior_confidence_choice, # type: ignore ) if self.value is not None: clone.set_value(self.value) @@ -122,26 +122,26 @@ def __eq__(self, other: Any) -> bool: self.choices == other.choices and self.value == other.value and self.is_fidelity == other.is_fidelity - and self.default == other.default + and self.prior == other.prior and self.has_prior == other.has_prior - and self.default_confidence_score == other.default_confidence_score + and self.prior_confidence_score == other.prior_confidence_score ) def __repr__(self) -> str: return f"" def _compute_user_prior_probabilities(self) -> npt.NDArray[f64]: - # The default value should have "default_confidence_score" more probability + # The prior value should have "prior_confidence_score" more probability # than all the other values. - assert self._default_index is not None + assert self._prior_index is not None probabilities = np.ones(len(self.choices)) - probabilities[self._default_index] = self.default_confidence_score + probabilities[self._prior_index] = self.prior_confidence_score return probabilities / np.sum(probabilities) @override def sample_value(self, *, user_priors: bool = False) -> Any: indices = np.arange(len(self.choices)) - if user_priors and self.default is not None: + if user_priors and self.prior is not None: probabilities = self._compute_user_prior_probabilities() return self.choices[np.random.choice(indices, p=probabilities)] @@ -179,16 +179,16 @@ def __init__( self, choices: Iterable[float | int | str], *, - default: float | int | str | None = None, - default_confidence: Literal["low", "medium", "high"] = "low", + prior: float | int | str | None = None, + prior_confidence: Literal["low", "medium", "high"] = "low", ): """Initialize a deprecated `CategoricalParameter`. Args: choices: choices for the hyperparameter. - default: default value for the hyperparameter, must be in `choices=` + prior: prior value for the hyperparameter, must be in `choices=` if provided. - default_confidence: confidence score for the default value, used when + prior_confidence: confidence score for the prior value, used when condsider prior based optimization. Raises: @@ -207,6 +207,6 @@ def __init__( ) super().__init__( choices=choices, - default=default, - default_confidence=default_confidence, + prior=prior, + prior_confidence=prior_confidence, ) diff --git a/neps/search_spaces/hyperparameters/constant.py b/neps/search_spaces/hyperparameters/constant.py index 2dab7bfd..155ddc9e 100644 --- a/neps/search_spaces/hyperparameters/constant.py +++ b/neps/search_spaces/hyperparameters/constant.py @@ -27,12 +27,12 @@ class Constant(Parameter[T, T]): !!! note As the name suggests, the value of a `Constant` only have one - value and so its [`.default`][neps.search_spaces.parameter.Parameter.default] + value and so its [`.prior`][neps.search_spaces.parameter.Parameter.prior] and [`.value`][neps.search_spaces.parameter.Parameter.value] should always be the same. This also implies that the - [`.default`][neps.search_spaces.parameter.Parameter.default] can never be `None`. + [`.prior`][neps.search_spaces.parameter.Parameter.prior] can never be `None`. Please use [`.set_constant_value()`][neps.search_spaces.hyperparameters.constant.Constant.set_constant_value] @@ -45,7 +45,7 @@ def __init__(self, value: T): Args: value: value for the hyperparameter. """ - super().__init__(value=value, default=value, is_fidelity=False) # type: ignore + super().__init__(value=value, prior=value, is_fidelity=False) # type: ignore self._value: T = value # type: ignore @override @@ -84,7 +84,7 @@ def set_value(self, value: T | None) -> None: [`.set_constant_value()`][neps.search_spaces.hyperparameters.constant.Constant.set_constant_value] which can be used to set both the [`.value`][neps.search_spaces.parameter.Parameter.value] - and the [`.default`][neps.search_spaces.parameter.Parameter.default] at once + and the [`.prior`][neps.search_spaces.parameter.Parameter.prior] at once Args: value: value to set the parameter to. @@ -94,7 +94,7 @@ def set_value(self, value: T | None) -> None: """ if value != self._value: raise ValueError( - f"Constant does not allow chaning the set value. " + f"Constant does not allow changing the set value. " f"Tried to set value to {value}, but it is already {self.value}" ) diff --git a/neps/search_spaces/hyperparameters/float.py b/neps/search_spaces/hyperparameters/float.py index f9003024..fef72f68 100644 --- a/neps/search_spaces/hyperparameters/float.py +++ b/neps/search_spaces/hyperparameters/float.py @@ -51,8 +51,8 @@ def __init__( *, log: bool = False, is_fidelity: bool = False, - default: Number | None = None, - default_confidence: Literal["low", "medium", "high"] = "low", + prior: Number | None = None, + prior_confidence: Literal["low", "medium", "high"] = "low", ): """Create a new `Float`. @@ -61,16 +61,16 @@ def __init__( upper: upper bound for the hyperparameter. log: whether the hyperparameter is on a log scale. is_fidelity: whether the hyperparameter is fidelity. - default: default value for the hyperparameter. - default_confidence: confidence score for the default value, used when + prior: prior value for the hyperparameter. + prior_confidence: confidence score for the prior value, used when condsidering prior based optimization.. """ super().__init__( lower=float(lower), upper=float(upper), log=log, - default=float(default) if default is not None else None, - default_confidence=default_confidence, + prior=float(prior) if prior is not None else None, + prior_confidence=prior_confidence, is_fidelity=is_fidelity, domain=Domain.floating(lower, upper, log=log), ) @@ -82,8 +82,8 @@ def clone(self) -> Self: upper=self.upper, log=self.log, is_fidelity=self.is_fidelity, - default=self.default, - default_confidence=self.default_confidence_choice, + prior=self.prior, + prior_confidence=self.prior_confidence_choice, ) if self.value is not None: clone.set_value(self.value) @@ -100,7 +100,7 @@ def set_value(self, value: float | None) -> None: if not self.lower <= value <= self.upper: cls_name = self.__class__.__name__ raise ValueError( - f"{cls_name} parameter: default bounds error. Expected lower <= default" + f"{cls_name} parameter: prior bounds error. Expected lower <= prior" f" <= upper, but got lower={self.lower}, value={value}," f" upper={self.upper}" ) @@ -114,13 +114,13 @@ def sample_value(self, *, user_priors: bool = False) -> float: if self.log: assert self.log_bounds is not None low, high = self.log_bounds - default = self.log_default + prior = self.log_prior else: - low, high, default = self.lower, self.upper, self.default + low, high, prior = self.lower, self.upper, self.prior if user_priors and self.has_prior: dist, std = self._get_truncnorm_prior_and_std() - value = dist.rvs() * std + default + value = dist.rvs() * std + prior else: value = np.random.uniform(low=low, high=high) @@ -171,8 +171,8 @@ def __init__( *, log: bool = False, is_fidelity: bool = False, - default: Number | None = None, - default_confidence: Literal["low", "medium", "high"] = "low", + prior: Number | None = None, + prior_confidence: Literal["low", "medium", "high"] = "low", ): """Initialize a deprecated `FloatParameter`. @@ -181,8 +181,8 @@ def __init__( upper: upper bound for the hyperparameter. log: whether the hyperparameter is on a log scale. is_fidelity: whether the hyperparameter is fidelity. - default: default value for the hyperparameter. - default_confidence: confidence score for the default value, used when + prior: prior value for the hyperparameter. + prior_confidence: confidence score for the prior value, used when condsidering prior based optimization.. Raises: @@ -204,6 +204,6 @@ def __init__( upper=upper, log=log, is_fidelity=is_fidelity, - default=default, - default_confidence=default_confidence, + prior=prior, + prior_confidence=prior_confidence, ) diff --git a/neps/search_spaces/hyperparameters/integer.py b/neps/search_spaces/hyperparameters/integer.py index 2644cd94..0386032d 100644 --- a/neps/search_spaces/hyperparameters/integer.py +++ b/neps/search_spaces/hyperparameters/integer.py @@ -47,8 +47,8 @@ def __init__( *, log: bool = False, is_fidelity: bool = False, - default: Number | None = None, - default_confidence: Literal["low", "medium", "high"] = "low", + prior: Number | None = None, + prior_confidence: Literal["low", "medium", "high"] = "low", ): """Create a new `Integer`. @@ -57,8 +57,8 @@ def __init__( upper: upper bound for the hyperparameter. log: whether the hyperparameter is on a log scale. is_fidelity: whether the hyperparameter is fidelity. - default: default value for the hyperparameter. - default_confidence: confidence score for the default value, used when + prior: prior value for the hyperparameter. + prior_confidence: confidence score for the prior value, used when condsider prior based optimization. """ lower = int(np.rint(lower)) @@ -75,8 +75,8 @@ def __init__( upper=int(np.rint(upper)), log=log, is_fidelity=is_fidelity, - default=int(np.rint(default)) if default is not None else None, - default_confidence=default_confidence, + prior=int(np.rint(prior)) if prior is not None else None, + prior_confidence=prior_confidence, domain=Domain.integer(lower, upper, log=log), ) @@ -88,8 +88,8 @@ def __init__( upper=self.upper + 0.499999, log=self.log, is_fidelity=is_fidelity, - default=default, - default_confidence=default_confidence, + prior=prior, + prior_confidence=prior_confidence, ) def __repr__(self) -> str: @@ -102,8 +102,8 @@ def clone(self) -> Self: upper=self.upper, log=self.log, is_fidelity=self.is_fidelity, - default=self.default, - default_confidence=self.default_confidence_choice, + prior=self.prior, + prior_confidence=self.prior_confidence_choice, ) if self.value is not None: clone.set_value(self.value) @@ -125,7 +125,7 @@ def set_value(self, value: int | None) -> None: if not self.lower <= value <= self.upper: cls_name = self.__class__.__name__ raise ValueError( - f"{cls_name} parameter: default bounds error. Expected lower <= default" + f"{cls_name} parameter: prior bounds error. Expected lower <= prior" f" <= upper, but got lower={self.lower}, value={value}," f" upper={self.upper}" ) @@ -164,8 +164,8 @@ def __init__( *, log: bool = False, is_fidelity: bool = False, - default: Number | None = None, - default_confidence: Literal["low", "medium", "high"] = "low", + prior: Number | None = None, + prior_confidence: Literal["low", "medium", "high"] = "low", ): """Initialize a deprecated `IntegerParameter`. @@ -174,8 +174,8 @@ def __init__( upper: upper bound for the hyperparameter. log: whether the hyperparameter is on a log scale. is_fidelity: whether the hyperparameter is fidelity. - default: default value for the hyperparameter. - default_confidence: confidence score for the default value, used when + prior: prior value for the hyperparameter. + prior_confidence: confidence score for the prior value, used when condsider prior based optimization. Raises: @@ -197,6 +197,6 @@ def __init__( upper=upper, log=log, is_fidelity=is_fidelity, - default=default, - default_confidence=default_confidence, + prior=prior, + prior_confidence=prior_confidence, ) diff --git a/neps/search_spaces/hyperparameters/numerical.py b/neps/search_spaces/hyperparameters/numerical.py index a6bd5673..e583eb03 100644 --- a/neps/search_spaces/hyperparameters/numerical.py +++ b/neps/search_spaces/hyperparameters/numerical.py @@ -13,7 +13,7 @@ base class for both of these hyperparameters, and includes methods from both [`ParameterWithPrior`][neps.search_spaces.ParameterWithPrior], allowing you to set a confidence along with a -[`.default`][neps.search_spaces.Parameter.default] that can be used +[`.prior`][neps.search_spaces.Parameter.prior] that can be used with certain algorithms. """ @@ -44,11 +44,11 @@ def _get_truncnorm_prior_and_std( low: int | float, high: int | float, - default: int | float, + prior: int | float, confidence_score: float, ) -> tuple[TruncNorm, float]: std = (high - low) * confidence_score - a, b = (low - default) / std, (high - default) / std + a, b = (low - prior) / std, (high - prior) / std return scipy.stats.truncnorm(a, b), float(std) @@ -60,10 +60,10 @@ class Numerical(ParameterWithPrior[T, T]): upper: The upper bound of the numerical hyperparameter. log: Whether the hyperparameter is in log space. log_bounds: The log bounds of the hyperparameter, if `log=True`. - log_default: The log default value of the hyperparameter, if `log=True` - and a `default` is set. - default_confidence_choice: The default confidence choice. - default_confidence_score: The default confidence score. + log_prior: The log prior value of the hyperparameter, if `log=True` + and a `prior` is set. + prior_confidence_choice: The prior confidence choice. + prior_confidence_score: The prior confidence score. has_prior: Whether the hyperparameter has a prior. """ @@ -75,10 +75,10 @@ def __init__( upper: T, *, log: bool = False, - default: T | None, + prior: T | None, is_fidelity: bool, domain: Domain[T], - default_confidence: Literal["low", "medium", "high"] = "low", + prior_confidence: Literal["low", "medium", "high"] = "low", ): """Initialize the numerical hyperparameter. @@ -86,12 +86,12 @@ def __init__( lower: The lower bound of the numerical hyperparameter. upper: The upper bound of the numerical hyperparameter. log: Whether the hyperparameter is in log space. - default: The default value of the hyperparameter. + prior: The prior value of the hyperparameter. is_fidelity: Whether the hyperparameter is a fidelity parameter. domain: The domain of the hyperparameter. - default_confidence: The default confidence choice. + prior_confidence: The prior confidence choice. """ - super().__init__(value=None, default=default, is_fidelity=is_fidelity) # type: ignore + super().__init__(value=None, prior=prior, is_fidelity=is_fidelity) # type: ignore _cls_name = self.__class__.__name__ if lower >= upper: raise ValueError( @@ -105,18 +105,18 @@ def __init__( f" Actual values: lower={lower}, upper={upper}" ) - if default is not None and not lower <= default <= upper: + if prior is not None and not lower <= prior <= upper: raise ValueError( - f"Float parameter: default bounds error. Expected lower <= default" - f" <= upper, but got lower={lower}, default={default}," + f"Float parameter: prior bounds error. Expected lower <= prior" + f" <= upper, but got lower={lower}, prior={prior}," f" upper={upper}" ) - if default_confidence not in self.DEFAULT_CONFIDENCE_SCORES: + if prior_confidence not in self.DEFAULT_CONFIDENCE_SCORES: raise ValueError( - f"{_cls_name} parameter: default confidence score error. Expected one of " + f"{_cls_name} parameter: prior confidence score error. Expected one of " f"{list(self.DEFAULT_CONFIDENCE_SCORES.keys())}, but got " - f"{default_confidence}" + f"{prior_confidence}" ) # Validate 'log' and 'is_fidelity' types to prevent configuration errors @@ -133,21 +133,17 @@ def __init__( self.log: bool = log self.domain: Domain[T] = domain self.log_bounds: tuple[float, float] | None = None - self.log_default: float | None = None + self.log_prior: float | None = None if self.log: self.log_bounds = (float(np.log(lower)), float(np.log(upper))) - self.log_default = ( - float(np.log(self.default)) if self.default is not None else None - ) + self.log_prior = float(np.log(self.prior)) if self.prior is not None else None - self.default_confidence_choice: Literal["low", "medium", "high"] = ( - default_confidence - ) + self.prior_confidence_choice: Literal["low", "medium", "high"] = prior_confidence - self.default_confidence_score: float = self.DEFAULT_CONFIDENCE_SCORES[ - default_confidence + self.prior_confidence_score: float = self.DEFAULT_CONFIDENCE_SCORES[ + prior_confidence ] - self.has_prior: bool = self.default is not None + self.has_prior: bool = self.prior is not None @override def __eq__(self, other: Any) -> bool: @@ -160,25 +156,25 @@ def __eq__(self, other: Any) -> bool: and self.log == other.log and self.is_fidelity == other.is_fidelity and self.value == other.value - and self.default == other.default - and self.default_confidence_score == other.default_confidence_score + and self.prior == other.prior + and self.prior_confidence_score == other.prior_confidence_score ) def _get_truncnorm_prior_and_std(self) -> tuple[TruncNorm, float]: if self.log: assert self.log_bounds is not None low, high = self.log_bounds - default = self.log_default + prior = self.log_prior else: low, high = self.lower, self.upper - default = self.default + prior = self.prior - assert default is not None + assert prior is not None return _get_truncnorm_prior_and_std( low=low, high=high, - default=default, - confidence_score=self.default_confidence_score, + prior=prior, + confidence_score=self.prior_confidence_score, ) @@ -195,10 +191,10 @@ def __init__( upper: T, *, log: bool = False, - default: T | None, + prior: T | None, is_fidelity: bool, domain: Domain[T], - default_confidence: Literal["low", "medium", "high"] = "low", + prior_confidence: Literal["low", "medium", "high"] = "low", ): """Initialize a deprecated `NumericalParameter`. @@ -206,10 +202,10 @@ def __init__( lower: The lower bound of the numerical hyperparameter. upper: The upper bound of the numerical hyperparameter. log: Whether the hyperparameter is in log space. - default: The default value of the hyperparameter. + prior: The prior value of the hyperparameter. is_fidelity: Whether the hyperparameter is a fidelity parameter. domain: The domain of the hyperparameter. - default_confidence: The default confidence choice. + prior_confidence: The prior confidence choice. Raises: DeprecationWarning: A warning indicating that `neps.NumericalParameter` is @@ -229,8 +225,8 @@ def __init__( lower=lower, upper=upper, log=log, - default=default, + prior=prior, is_fidelity=is_fidelity, domain=domain, - default_confidence=default_confidence, + prior_confidence=prior_confidence, ) diff --git a/neps/search_spaces/parameter.py b/neps/search_spaces/parameter.py index ea94a84d..f8b763cb 100644 --- a/neps/search_spaces/parameter.py +++ b/neps/search_spaces/parameter.py @@ -30,7 +30,7 @@ class Parameter(ABC, Generic[ValueT, SerializedT]): """A base class for hyperparameters. Attributes: - default: default value for the hyperparameter. This value + prior: default value for the hyperparameter. This value is used as a prior to inform algorithms about a decent default value for the hyperparameter, as well as use attributes from [`ParameterWithPrior`][neps.search_spaces.ParameterWithPrior], @@ -44,17 +44,17 @@ def __init__( self, *, value: ValueT | None, - default: ValueT | None, + prior: ValueT | None, is_fidelity: bool, ): """Create a new `Parameter`. Args: value: value for the hyperparameter. - default: default value for the hyperparameter. + prior: default value for the hyperparameter. is_fidelity: whether the hyperparameter is fidelity. """ - self.default = default + self.prior = prior self.is_fidelity = is_fidelity # TODO(eddiebergman): The reason to have this not as a straight alone @@ -69,7 +69,7 @@ def __init__( ) # TODO: Pass in through subclasses - self.default_confidence_score: float + self.prior_confidence_score: float # TODO(eddiebergman): All this does is just check values which highly unlikely # what we want. However this needs to be tackled in a seperate PR. @@ -165,16 +165,16 @@ class ParameterWithPrior(Parameter[ValueT, SerializedT]): """A base class for hyperparameters with priors. Attributes: - default_confidence_choice: The choice of how confident any algorithm should - be in the default value being a good value. - default_confidence_score: A score used by algorithms to utilize the default value. + prior_confidence_choice: The choice of how confident any algorithm should + be in the prior value being a good value. + prior_confidence_score: A score used by algorithms to utilize the prior value. has_prior: whether the hyperparameter has a prior that can be used by an - algorithm. In many cases, this refers to having a default value. + algorithm. In many cases, this refers to having a prior value. """ DEFAULT_CONFIDENCE_SCORES: ClassVar[Mapping[str, float]] - default_confidence_choice: str - default_confidence_score: float + prior_confidence_choice: str + prior_confidence_score: float has_prior: bool # NOTE(eddiebergman): Like the normal `Parameter.sample` but with `user_priors`. diff --git a/neps/search_spaces/search_space.py b/neps/search_spaces/search_space.py index a1566733..3cb69b85 100644 --- a/neps/search_spaces/search_space.py +++ b/neps/search_spaces/search_space.py @@ -61,26 +61,26 @@ def pipeline_space_from_configspace( elif isinstance(hyperparameter, CS.CategoricalHyperparameter): parameter = Categorical( hyperparameter.choices, - default=hyperparameter.default_value, + prior=hyperparameter.default_value, ) elif isinstance(hyperparameter, CS.OrdinalHyperparameter): parameter = Categorical( hyperparameter.sequence, - default=hyperparameter.default_value, + prior=hyperparameter.default_value, ) elif isinstance(hyperparameter, CS.UniformIntegerHyperparameter): parameter = Integer( lower=hyperparameter.lower, upper=hyperparameter.upper, log=hyperparameter.log, - default=hyperparameter.default_value, + prior=hyperparameter.default_value, ) elif isinstance(hyperparameter, CS.UniformFloatHyperparameter): parameter = Float( lower=hyperparameter.lower, upper=hyperparameter.upper, log=hyperparameter.log, - default=hyperparameter.default_value, + prior=hyperparameter.default_value, ) else: raise ValueError(f"Unknown hyperparameter type {hyperparameter}") @@ -219,27 +219,27 @@ def __init__(self, **hyperparameters: Parameter): # noqa: C901, PLR0912 self.fidelity_name: str | None = _fidelity_name self.has_prior: bool = _has_prior - self.default_config = {} + self.prior_config = {} for name, hp in _hyperparameters: - if hp.default is not None: - self.default_config[name] = hp.default + if hp.prior is not None: + self.prior_config[name] = hp.prior continue match hp: case Categorical(): first_choice = hp.choices[0] - self.default_config[name] = first_choice + self.prior_config[name] = first_choice case Integer() | Float(): if hp.is_fidelity: - self.default_config[name] = hp.upper + self.prior_config[name] = hp.upper continue midpoint = hp.domain.cast_one(0.5, frm=UNIT_FLOAT_DOMAIN) - self.default_config[name] = midpoint + self.prior_config[name] = midpoint case Constant(): - self.default_config[name] = hp.value + self.prior_config[name] = hp.value case GraphParameter(): - self.default_config[name] = hp.default + self.prior_config[name] = hp.prior case _: raise TypeError(f"Unknown hyperparameter type {hp}") diff --git a/neps/state/_eval.py b/neps/state/_eval.py index f3153a64..915e3823 100644 --- a/neps/state/_eval.py +++ b/neps/state/_eval.py @@ -32,15 +32,17 @@ def parse_user_result( user_result: float | dict[str, Any], *, default_cost_value: float | None = None, - default_learning_curve: Literal["loss"] | list[float] | None = None, + default_learning_curve: Literal["objective_to_minimize"] | list[float] | None = None, ) -> tuple[float, float | None, list[float] | None, dict[str, Any]]: """Check if the trial has succeeded.""" if isinstance(user_result, Mapping): - extracted_loss = user_result.pop("loss", _notset) - if extracted_loss is _notset: + extracted_objective_to_minimize = user_result.pop( + "objective_to_minimize", _notset + ) + if extracted_objective_to_minimize is _notset: raise KeyError( - "The 'loss' should be provided in the evaluation result if providing" - " a dictionary." + "The 'objective_to_minimize' should be provided in the evaluation result" + " if providing a dictionary." ) extracted_cost = user_result.pop("cost", default_cost_value) @@ -56,30 +58,32 @@ def parse_user_result( else: extracted_learning_curve = default_learning_curve - if extracted_learning_curve == "loss": - extracted_learning_curve = [extracted_loss] + if extracted_learning_curve == "objective_to_minimize": + extracted_learning_curve = [extracted_objective_to_minimize] extra = user_result else: - extracted_loss = user_result + extracted_objective_to_minimize = user_result extracted_learning_curve = ( None if default_learning_curve is None else [user_result] - if default_learning_curve == "loss" + if default_learning_curve == "objective_to_minimize" else default_learning_curve ) extracted_cost = default_cost_value extra = {} - loss = _check_float(extracted_loss, "loss") + objective_to_minimize = _check_float( + extracted_objective_to_minimize, "objective_to_minimize" + ) cost = _check_float(extracted_cost, "cost") if extracted_cost is not None else None learning_curve = ( [float(v) for v in extracted_learning_curve] if extracted_learning_curve is not None else None ) - return loss, cost, learning_curve, extra + return objective_to_minimize, cost, learning_curve, extra def _eval_trial( @@ -100,7 +104,7 @@ def _eval_trial( logger.exception(e) report = trial.set_complete( report_as="crashed", - loss=default_report_values.loss_value_on_error, + objective_to_minimize=default_report_values.objective_to_minimize_value_on_error, cost=default_report_values.cost_value_on_error, learning_curve=default_report_values.learning_curve_on_error, extra=None, @@ -114,14 +118,14 @@ def _eval_trial( time_end = time.time() logger.info(f"Successful evaluation of '{trial.id}': {user_result}.") - loss, cost, learning_curve, extra = parse_user_result( + objective_to_minimize, cost, learning_curve, extra = parse_user_result( dict(user_result) if isinstance(user_result, Mapping) else user_result, default_cost_value=default_report_values.cost_if_not_provided, default_learning_curve=default_report_values.learning_curve_if_not_provided, ) report = trial.set_complete( report_as="success", - loss=loss, + objective_to_minimize=objective_to_minimize, cost=cost, learning_curve=learning_curve, err=None, diff --git a/neps/state/filebased.py b/neps/state/filebased.py index cf53c622..152893d7 100644 --- a/neps/state/filebased.py +++ b/neps/state/filebased.py @@ -392,7 +392,7 @@ def read(cls, directory: Path) -> OptimizationState: budget = BudgetInfo(**budget_info) if budget_info is not None else None return OptimizationState( shared_state=state.get("shared_state") or {}, - budget=budget, + max_cost_total_info=budget, ) @override diff --git a/neps/state/neps_state.py b/neps/state/neps_state.py index 1ed3f67b..f60718d4 100644 --- a/neps/state/neps_state.py +++ b/neps/state/neps_state.py @@ -103,10 +103,14 @@ def sample_trial( optimizer = hook(optimizer) # NOTE: We don't want optimizers mutating this before serialization - budget = opt_state.budget.clone() if opt_state.budget is not None else None + max_cost_total = ( + opt_state.max_cost_total_info.clone() + if opt_state.max_cost_total_info is not None + else None + ) sampled_config_maybe_new_opt_state = optimizer.ask( trials=trials, - budget_info=budget, + max_cost_total_info=max_cost_total, ) if isinstance(sampled_config_maybe_new_opt_state, tuple): @@ -138,7 +142,10 @@ def sample_trial( seed_state.recapture() put_seed_state(seed_state) put_opt( - OptimizationState(budget=opt_state.budget, shared_state=new_opt_state) + OptimizationState( + max_cost_total_info=opt_state.max_cost_total_info, + shared_state=new_opt_state, + ) ) return trial @@ -169,11 +176,11 @@ def report_trial_evaluation( with self._optimizer_state.acquire() as (opt_state, put_opt_state): # TODO: If an optimizer doesn't use the state, this is a waste of time. # Update the budget if we have one. - if opt_state.budget is not None: - budget_info = opt_state.budget + if opt_state.max_cost_total_info is not None: + max_cost_total_info = opt_state.max_cost_total_info if report.cost is not None: - budget_info.used_cost_budget += report.cost + max_cost_total_info.used_cost_budget += report.cost put_opt_state(opt_state) if report.err is not None: diff --git a/neps/state/optimizer.py b/neps/state/optimizer.py index 38d0bfb5..f8981e6c 100644 --- a/neps/state/optimizer.py +++ b/neps/state/optimizer.py @@ -11,7 +11,7 @@ class BudgetInfo: """Information about the budget of an optimizer.""" - max_cost_budget: float | None = None + max_cost_total: float | None = None used_cost_budget: float = 0.0 max_evaluations: int | None = None used_evaluations: int = 0 @@ -25,7 +25,7 @@ def clone(self) -> BudgetInfo: class OptimizationState: """The current state of an optimizer.""" - budget: BudgetInfo | None + max_cost_total_info: BudgetInfo | None """Information regarind the budget used by the optimization trajectory.""" shared_state: dict[str, Any] diff --git a/neps/state/settings.py b/neps/state/settings.py index f34a9435..def6a230 100644 --- a/neps/state/settings.py +++ b/neps/state/settings.py @@ -11,8 +11,8 @@ class DefaultReportValues: """Values to use when an error occurs.""" - loss_value_on_error: float | None = None - """The value to use for the loss when an error occurs.""" + objective_to_minimize_value_on_error: float | None = None + """The value to use for the objective_to_minimize when an error occurs.""" cost_value_on_error: float | None = None """The value to use for the cost when an error occurs.""" @@ -23,11 +23,13 @@ class DefaultReportValues: learning_curve_on_error: list[float] | None = None """The value to use for the learning curve when an error occurs. - If `'loss'`, the learning curve will be set to the loss value but as - a list with a single value. + If `'objective_to_minimize'`, the learning curve will be set to the + objective_to_minimize value but as a list with a single value. """ - learning_curve_if_not_provided: Literal["loss"] | list[float] | None = None + learning_curve_if_not_provided: ( + Literal["objective_to_minimize"] | list[float] | None + ) = None """The value to use for the learning curve when the evaluation function does not provide one.""" diff --git a/neps/state/trial.py b/neps/state/trial.py index 75ad0664..d80c3fbc 100644 --- a/neps/state/trial.py +++ b/neps/state/trial.py @@ -65,7 +65,7 @@ class Report: """A failed report of the evaluation of a configuration.""" trial_id: str - loss: float | None + objective_to_minimize: float | None cost: float | None learning_curve: list[float] | None # TODO: Serializing a large list into yaml sucks! extra: Mapping[str, Any] @@ -81,12 +81,16 @@ def __post_init__(self) -> None: def to_deprecate_result_dict(self) -> dict[str, Any] | ERROR: """Return the report as a dictionary.""" if self.reported_as == "success": - d = {"loss": self.loss, "cost": self.cost, **self.extra} + d = { + "objective_to_minimize": self.objective_to_minimize, + "cost": self.cost, + **self.extra, + } # HACK: Backwards compatibility. Not sure how much this is needed # but it should be removed once optimizers stop calling the - # `get_loss`, `get_cost`, `get_learning_curve` methods of `BaseOptimizer` - # and just use the `Report` directly. + # `get_objective_to_minimize`, `get_cost`, `get_learning_curve` methods of + # `BaseOptimizer` and just use the `Report` directly. if "info_dict" not in d or "learning_curve" not in d["info_dict"]: d.setdefault("info_dict", {})["learning_curve"] = self.learning_curve return d @@ -94,8 +98,8 @@ def to_deprecate_result_dict(self) -> dict[str, Any] | ERROR: return "error" def __eq__(self, value: Any, /) -> bool: - # HACK : Since it could be probably that one of loss or cost is nan, - # we need a custom comparator for this object + # HACK : Since it could be probably that one of objective_to_minimize or cost is + # nan, we need a custom comparator for this object # HACK : We also have to skip over the `Err` object since when it's deserialized, # we can not recover the original object/type. if not isinstance(value, Report): @@ -110,7 +114,7 @@ def __eq__(self, value: Any, /) -> bool: if k == "err": if str(v) != str(other_v): return False - elif k in ("loss", "cost"): + elif k in ("objective_to_minimize", "cost"): if v is not None and np.isnan(v): if other_v is None or not np.isnan(other_v): return False @@ -182,7 +186,7 @@ def into_config_result( if self.report.reported_as == "success": result = { **self.report.extra, - "loss": self.report.loss, + "objective_to_minimize": self.report.objective_to_minimize, "cost": self.report.cost, } else: @@ -211,7 +215,7 @@ def set_complete( *, report_as: Literal["success", "failed", "crashed"], time_end: float, - loss: float | None, + objective_to_minimize: float | None, cost: float | None, learning_curve: list[float] | None, err: Exception | None, @@ -234,7 +238,9 @@ def set_complete( extra = {} if extra is None else extra - loss = float(loss) if loss is not None else None + objective_to_minimize = ( + float(objective_to_minimize) if objective_to_minimize is not None else None + ) cost = float(cost) if cost is not None else None if learning_curve is not None: learning_curve = [float(v) for v in learning_curve] @@ -243,7 +249,7 @@ def set_complete( trial_id=self.metadata.id, reported_as=report_as, evaluation_duration=evaluation_duration, - loss=loss, + objective_to_minimize=objective_to_minimize, cost=cost, learning_curve=learning_curve, extra=extra, diff --git a/neps/status/__main__.py b/neps/status/__main__.py index 744a3417..b1266937 100644 --- a/neps/status/__main__.py +++ b/neps/status/__main__.py @@ -1,18 +1,20 @@ """Displays status information about a working directory of a neps.run. Usage: - python -m neps.status [-h] [--best_losses] [--best_configs] [--all_configs] - working_directory + python -m neps.status [-h] [--best_objective_to_minimizees] [--best_configs] + [--all_configs] working_directory Positional arguments: working_directory The working directory given to neps.run Optional arguments: -h, --help show this help message and exit - --best_losses Show the trajectory of the best loss across evaluations - --best_configs Show the trajectory of the best configs and their losses + --best_objective_to_minimizees Show the trajectory of the best + objective_to_minimize across evaluations + --best_configs Show the trajectory of the best configs and their + objective_to_minimizees across evaluations - --all_configs Show all configs and their losses + --all_configs Show all configs and their objective_to_minimizees Note: We have to use the __main__.py construct due to the issues explained in @@ -36,14 +38,15 @@ parser.add_argument("root_directory", type=Path, help="The working directory given to neps.run") parser.add_argument( - "--best_losses", + "--best_objective_to_minimizees", action="store_true", - help="Show the trajectory of the best loss across evaluations", + help="Show the trajectory of the best objective_to_minimize across evaluations", ) parser.add_argument( "--best_configs", action="store_true", - help="Show the trajectory of the best configs and their losses across evaluations", + help="Show the trajectory of the best configs and their losses " + "across evaluations", ) parser.add_argument( "--all_configs", diff --git a/neps/status/status.py b/neps/status/status.py index 660c53f3..5f6656a5 100644 --- a/neps/status/status.py +++ b/neps/status/status.py @@ -77,7 +77,7 @@ def get_summary_dict( summary["num_pending_configs"] = len(in_progress) + len(pending) summary["num_pending_configs_with_worker"] = len(in_progress) - summary["best_loss"] = float("inf") + summary["best_objective_to_minimize"] = float("inf") summary["best_config_id"] = None summary["best_config_metadata"] = None summary["best_config"] = None @@ -85,9 +85,12 @@ def get_summary_dict( for evaluation in evaluated.values(): if evaluation.result == "error": summary["num_error"] += 1 - loss = evaluation.loss - if isinstance(loss, float) and loss < summary["best_loss"]: - summary["best_loss"] = loss + objective_to_minimize = evaluation.objective_to_minimize + if ( + isinstance(objective_to_minimize, float) + and objective_to_minimize < summary["best_objective_to_minimize"] + ): + summary["best_objective_to_minimize"] = objective_to_minimize summary["best_config"] = evaluation.config summary["best_config_id"] = evaluation.id summary["best_config_metadata"] = evaluation.metadata @@ -107,7 +110,8 @@ def status( Args: root_directory: The root directory given to neps.run. - best_losses: If true, show the trajectory of the best loss across evaluations + best_losses: If true, show the trajectory of the best objective_to_minimize + across evaluations best_configs: If true, show the trajectory of the best configs and their losses across evaluations all_configs: If true, show all configs and their losses @@ -133,29 +137,35 @@ def status( return summary["previous_results"], summary["pending_configs"] print() - print(f"Best loss: {summary['best_loss']}") + print(f"Best objective_to_minimize: {summary['best_objective_to_minimize']}") print(f"Best config id: {summary['best_config_id']}") print(f"Best config: {summary['best_config']}") if best_losses: print() - print("Best loss across evaluations:") - best_loss_trajectory = root_directory / "best_loss_trajectory.txt" - print(best_loss_trajectory.read_text(encoding="utf-8")) + print("Best objective_to_minimize across evaluations:") + best_objective_to_minimize_trajectory = ( + root_directory / "best_objective_to_minimize_trajectory.txt" + ) + print(best_objective_to_minimize_trajectory.read_text(encoding="utf-8")) if best_configs: print() print("Best configs and their losses across evaluations:") print(79 * "-") - best_loss_config = root_directory / "best_loss_with_config_trajectory.txt" - print(best_loss_config.read_text(encoding="utf-8")) + best_objective_to_minimize_config = ( + root_directory / "best_objective_to_minimize_with_config_trajectory.txt" + ) + print(best_objective_to_minimize_config.read_text(encoding="utf-8")) if all_configs: print() print("All evaluated configs and their losses:") print(79 * "-") - all_loss_config = root_directory / "all_losses_and_configs.txt" - print(all_loss_config.read_text(encoding="utf-8")) + all_objective_to_minimize_config = ( + root_directory / "all_losses_and_configs.txt" + ) + print(all_objective_to_minimize_config.read_text(encoding="utf-8")) return summary["previous_results"], summary["pending_configs"] @@ -264,7 +274,7 @@ def _get_dataframes_from_summary( "num_evaluated_configs": summary["num_evaluated_configs"], "num_pending_configs": summary["num_pending_configs"], "num_pending_configs_with_worker": summary["num_pending_configs_with_worker"], - "best_loss": summary["best_loss"], + "best_objective_to_minimize": summary["best_objective_to_minimize"], "best_config_id": summary["best_config_id"], "num_error": summary["num_error"], } @@ -309,7 +319,7 @@ def _save_data_to_csv( # Represents the last worker if int(pending_configs) == 0 and int(pending_configs_with_worker) == 0: config_data_df = config_data_df.sort_values( - by="result.loss", + by="result.objective_to_minimize", ascending=True, ) config_data_df.to_csv(config_data_file_path, index=False, mode="w") @@ -330,7 +340,7 @@ def _save_data_to_csv( # check if the current worker has more evaluated configs than the previous if int(num_evaluated_configs_csv) < num_evaluated_configs_run.iloc[0]: config_data_df = config_data_df.sort_values( - by="result.loss", + by="result.objective_to_minimize", ascending=True, ) config_data_df.to_csv(config_data_file_path, index=False, mode="w") @@ -338,7 +348,7 @@ def _save_data_to_csv( # Represents the first worker to be evaluated else: config_data_df = config_data_df.sort_values( - by="result.loss", + by="result.objective_to_minimize", ascending=True, ) config_data_df.to_csv(config_data_file_path, index=False, mode="w") diff --git a/neps/utils/cli.py b/neps/utils/cli.py index 1ad92d4a..67d67a7b 100644 --- a/neps/utils/cli.py +++ b/neps/utils/cli.py @@ -20,7 +20,7 @@ import pandas as pd from neps.utils.run_args import ( RUN_ARGS, - RUN_PIPELINE, + EVALUATE_PIPELINE, ROOT_DIRECTORY, POST_RUN_SUMMARY, MAX_EVALUATIONS_PER_RUN, @@ -32,7 +32,7 @@ SEARCHER, SEARCHER_KWARGS, IGNORE_ERROR, - LOSS_VALUE_ON_ERROR, + OBJECTIVE_TO_MINIMIZE_VALUE_ON_ERROR, COST_VALUE_ON_ERROR, CONTINUE_UNTIL_MAX_EVALUATION_COMPLETED, OVERWRITE_WORKING_DIRECTORY, @@ -144,8 +144,8 @@ def init_config(args: argparse.Namespace) -> None: directory=directory, optimizer_info=OptimizerInfo(optimizer_info), optimizer_state=OptimizationState( - budget=( - BudgetInfo(max_cost_budget=max_cost_total, used_cost_budget=0) + max_cost_total_info=( + BudgetInfo(max_cost_total=max_cost_total, used_cost_budget=0) if max_cost_total is not None else None ), @@ -172,8 +172,8 @@ def init_config(args: argparse.Namespace) -> None: file.write( """# Add your NEPS configuration settings here -run_pipeline: - path: "path/to/your/run_pipeline.py" +evaluate_pipeline: + path: "path/to/your/evaluate_pipeline.py" name: name_of_your_pipeline_function pipeline_space: @@ -199,8 +199,8 @@ def init_config(args: argparse.Namespace) -> None: file.write( """# Full Configuration Template for NePS -run_pipeline: - path: path/to/your/run_pipeline.py # Path to the function file +evaluate_pipeline: + path: path/to/your/evaluate_pipeline.py # Path to the function file name: example_pipeline # Function name within the file pipeline_space: @@ -231,7 +231,7 @@ def init_config(args: argparse.Namespace) -> None: continue_until_max_evaluation_completed: true # Error Handling -loss_value_on_error: +objective_to_minimize_value_on_error: cost_value_on_error: ignore_errors: @@ -286,12 +286,14 @@ def run_optimization(args: argparse.Namespace) -> None: run_args = Path("run_config.yaml") else: run_args = args.run_args - if not isinstance(args.run_pipeline, Default): - module_path, function_name = args.run_pipeline.split(":") - run_pipeline = load_and_return_object(module_path, function_name, RUN_PIPELINE) + if not isinstance(args.evaluate_pipeline, Default): + module_path, function_name = args.evaluate_pipeline.split(":") + evaluate_pipeline = load_and_return_object( + module_path, function_name, EVALUATE_PIPELINE + ) else: - run_pipeline = args.run_pipeline + evaluate_pipeline = args.evaluate_pipeline kwargs = {} if args.searcher_kwargs: @@ -300,7 +302,7 @@ def run_optimization(args: argparse.Namespace) -> None: # Collect arguments from args and prepare them for neps.run options = { RUN_ARGS: run_args, - RUN_PIPELINE: run_pipeline, + EVALUATE_PIPELINE: evaluate_pipeline, PIPELINE_SPACE: args.pipeline_space, ROOT_DIRECTORY: args.root_directory, OVERWRITE_WORKING_DIRECTORY: args.overwrite_working_directory, @@ -314,7 +316,7 @@ def run_optimization(args: argparse.Namespace) -> None: ), MAX_COST_TOTAL: args.max_cost_total, IGNORE_ERROR: args.ignore_errors, - LOSS_VALUE_ON_ERROR: args.loss_value_on_error, + OBJECTIVE_TO_MINIMIZE_VALUE_ON_ERROR: args.objective_to_minimize_value_on_error, COST_VALUE_ON_ERROR: args.cost_value_on_error, SEARCHER: args.searcher, **kwargs, @@ -359,7 +361,7 @@ def info_config(args: argparse.Namespace) -> None: if trial.report is not None: print("\nReport:") - print(f" Loss: {trial.report.loss}") + print(f" Objective_to_minimize: {trial.report.objective_to_minimize}") print(f" Cost: {trial.report.cost}") print(f" Reported As: {trial.report.reported_as}") error = trial.report.err @@ -508,7 +510,7 @@ def status(args: argparse.Namespace) -> None: print(f"Failed Trials (Errors): {failed_trials_count}") print(f"Active Trials: {evaluating_trials_count}") print(f"Pending Trials: {pending_trials_count}") - print(f"Best Loss Achieved: {summary['best_loss']}") + print(f"Best Objective_to_minimize Achieved: {summary['best_objective_to_minimize']}") print("\nLatest Trials:") print("-----------------------------") @@ -542,7 +544,12 @@ def status(args: argparse.Namespace) -> None: # Print the header print( header_format.format( - "Sampled Time", "Duration", "Trial ID", "Worker ID", "State", "Loss" + "Sampled Time", + "Duration", + "Trial ID", + "Worker ID", + "State", + "Objective_to_minimize", ) ) @@ -560,13 +567,17 @@ def status(args: argparse.Namespace) -> None: trial_id = trial.id worker_id = trial.metadata.sampling_worker_id state = trial.state.name - loss = ( - f"{trial.report.loss:.6f}" - if (trial.report and trial.report.loss is not None) + objective_to_minimize = ( + f"{trial.report.objective_to_minimize:.6f}" + if (trial.report and trial.report.objective_to_minimize is not None) else "N/A" ) - print(row_format.format(time_sampled, duration, trial_id, worker_id, state, loss)) + print( + row_format.format( + time_sampled, duration, trial_id, worker_id, state, objective_to_minimize + ) + ) # If no specific filter is applied, print the best trial and optimizer info if not args.pending and not args.evaluating and not args.succeeded: @@ -574,7 +585,7 @@ def status(args: argparse.Namespace) -> None: print("\nBest Trial:") print("-----------------------------") print(f"ID: {summary['best_config_id']}") - print(f"Loss: {summary['best_loss']}") + print(f"Loss: {summary['best_objective_to_minimize']}") print("Config:") for key, value in summary["best_config"].items(): print(f" {key}: {value}") @@ -675,12 +686,15 @@ def load_neps_state(directory_path: Path) -> Optional[NePSState[Path]]: def compute_incumbents(sorted_trials: List[Trial]) -> List[Trial]: - """Compute the list of incumbent trials based on the best loss.""" - best_loss = float("inf") + """Compute the list of incumbent trials based on the best objective_to_minimize.""" + best_objective_to_minimize = float("inf") incumbents = [] for trial in sorted_trials: - if trial.report and trial.report.loss < best_loss: - best_loss = trial.report.loss + if ( + trial.report + and trial.report.objective_to_minimize < best_objective_to_minimize + ): + best_objective_to_minimize = trial.report.objective_to_minimize incumbents.append(trial) return incumbents[::-1] # Reverse for most recent first @@ -784,11 +798,16 @@ def display_results(directory_path: Path, incumbents: List[Trial]) -> None: print("-" * len(header)) if incumbents: for trial in incumbents: - if trial.report is not None and trial.report.loss is not None: + if ( + trial.report is not None + and trial.report.objective_to_minimize is not None + ): config = ", ".join(f"{k}: {v}" for k, v in trial.config.items()) - print(f"{trial.id:<6} {trial.report.loss:<12.6f} {config:<60}") + print( + f"{trial.id:<6} {trial.report.objective_to_minimize:<12.6f} {config:<60}" + ) else: - print(f"Trial {trial.id} has no valid loss.") + print(f"Trial {trial.id} has no valid objective_to_minimize.") else: print("No Incumbent Trials found.") @@ -802,9 +821,10 @@ def plot_incumbents( # Collect data for plotting x_values = [id_to_index[incumbent.id] for incumbent in incumbents] y_values = [ - incumbent.report.loss + incumbent.report.objective_to_minimize for incumbent in incumbents - if incumbent.report is not None and incumbent.report.loss is not None + if incumbent.report is not None + and incumbent.report.objective_to_minimize is not None ] plt.figure(figsize=(12, 6)) @@ -879,7 +899,7 @@ def print_help(args: Optional[argparse.Namespace] = None) -> None: --continue-until-max-evaluation-completed (Continue until max evaluations are completed.) --max-cost-total (Max cost before halting new evaluations.) --ignore-errors (Ignore errors during optimization.) - --loss-value-on-error (Assumed loss value on error.) + --objective_to_minimize-value-on-error (Assumed objective_to_minimize value on error.) --cost-value-on-error (Assumed cost value on error.) --searcher (Searcher algorithm key for optimization.) --searcher-kwargs ... (Additional kwargs for the searcher.) @@ -1043,7 +1063,7 @@ def handle_report_config(args: argparse.Namespace) -> None: report = trial.set_complete( report_as=args.reported_as, time_end=args.time_end, - loss=args.loss, + objective_to_minimize=args.objective_to_minimize, cost=args.cost, learning_curve=args.learning_curve, err=Exception(args.err) if args.err else None, @@ -1067,7 +1087,9 @@ def handle_report_config(args: argparse.Namespace) -> None: print(f"Trial ID: {trial.metadata.id}") print(f"Reported As: {report.reported_as}") print(f"Time Ended: {convert_timestamp(trial.metadata.time_end)}") - print(f"Loss: {report.loss if report.loss is not None else 'N/A'}") + print( + f"Loss: {report.objective_to_minimize if report.objective_to_minimize is not None else 'N/A'}" + ) print(f"Cost: {report.cost if report.cost is not None else 'N/A'}") print(f"Evaluation Duration: {format_duration(report.evaluation_duration)}") @@ -1104,7 +1126,9 @@ def load_optimizer(run_args: dict) -> Tuple[Optional[BaseOptimizer], Optional[di pipeline_space=run_args.get(PIPELINE_SPACE), max_cost_total=run_args.get(MAX_COST_TOTAL, None), ignore_errors=run_args.get(IGNORE_ERROR, False), - loss_value_on_error=run_args.get(LOSS_VALUE_ON_ERROR, None), + objective_to_minimize_value_on_error=run_args.get( + OBJECTIVE_TO_MINIMIZE_VALUE_ON_ERROR, None + ), cost_value_on_error=run_args.get(COST_VALUE_ON_ERROR, None), searcher=run_args.get(SEARCHER, "default"), **run_args.get(SEARCHER_KWARGS, {}), @@ -1185,7 +1209,7 @@ def main() -> None: type=str, help="Optional: Provide the path to a Python file and a function name separated " "by a colon, e.g., 'path/to/module.py:function_name'. " - "If provided, it overrides the run_pipeline setting from the YAML " + "If provided, it overrides the evaluate_pipeline setting from the YAML " "configuration.", default=Default(None), ) @@ -1210,7 +1234,7 @@ def main() -> None: action="store_true", default=Default(False), # noqa: FBT003 help="If set, deletes the working directory at the start of the run. " - "This is useful, for example, when debugging a run_pipeline function. " + "This is useful, for example, when debugging a evaluate_pipeline function. " "(default: %(default)s)", ) parser_run.add_argument( @@ -1266,8 +1290,8 @@ def main() -> None: type=float, default=Default(None), help="No new evaluations will start when this cost is exceeded. Requires " - "returning a cost in the run_pipeline function, e.g., `return dict(" - "loss=loss, cost=cost)`. (default: %(default)s)", + "returning a cost in the evaluate_pipeline function, e.g., `return dict(" + "objective_to_minimize=objective_to_minimize, cost=cost)`. (default: %(default)s)", ) parser_run.add_argument( "--ignore-errors", @@ -1277,7 +1301,7 @@ def main() -> None: "default)s)", ) parser_run.add_argument( - "--loss-value-on-error", + "--objective_to_minimize-value-on-error", type=float, default=Default(None), help="Loss value to assume on error. (default: %(default)s)", @@ -1371,7 +1395,9 @@ def main() -> None: default="cli", help="The worker ID for which the configuration is being sampled.", ) - report_parser.add_argument("--loss", type=float, help="Loss value of the trial") + report_parser.add_argument( + "--objective_to_minimize", type=float, help="Loss value of the trial" + ) report_parser.add_argument( "--run-args", type=str, help="Path to the YAML file containing run configurations" ) diff --git a/neps/utils/run_args.py b/neps/utils/run_args.py index 9f5cc60d..2fbd740c 100644 --- a/neps/utils/run_args.py +++ b/neps/utils/run_args.py @@ -20,7 +20,7 @@ # Define the name of the arguments as variables for easier code maintenance RUN_ARGS = "run_args" -RUN_PIPELINE = "run_pipeline" +EVALUATE_PIPELINE = "evaluate_pipeline" PIPELINE_SPACE = "pipeline_space" ROOT_DIRECTORY = "root_directory" MAX_EVALUATIONS_TOTAL = "max_evaluations_total" @@ -30,7 +30,7 @@ DEVELOPMENT_STAGE_ID = "development_stage_id" TASK_ID = "task_id" CONTINUE_UNTIL_MAX_EVALUATION_COMPLETED = "continue_until_max_evaluation_completed" -LOSS_VALUE_ON_ERROR = "loss_value_on_error" +OBJECTIVE_TO_MINIMIZE_VALUE_ON_ERROR = "objective_to_minimize_value_on_error" COST_VALUE_ON_ERROR = "cost_value_on_error" IGNORE_ERROR = "ignore_errors" SEARCHER = "searcher" @@ -49,7 +49,7 @@ def get_run_args_from_yaml(path: str | Path) -> dict: 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. + objects(e.g. evaluate_pipeline) requiring loading. Args: path (str): The file path to the YAML configuration file. @@ -67,7 +67,7 @@ def get_run_args_from_yaml(path: str | Path) -> dict: settings = {} # List allowed NePS run arguments with simple types (e.g., string, int). Parameters - # like 'run_pipeline', 'preload_hooks', 'pipeline_space', + # like 'evaluate_pipeline', 'preload_hooks', 'pipeline_space', # and 'searcher' are excluded due to needing specialized processing. expected_parameters = [ ROOT_DIRECTORY, @@ -79,7 +79,7 @@ def get_run_args_from_yaml(path: str | Path) -> dict: TASK_ID, MAX_EVALUATIONS_PER_RUN, CONTINUE_UNTIL_MAX_EVALUATION_COMPLETED, - LOSS_VALUE_ON_ERROR, + OBJECTIVE_TO_MINIMIZE_VALUE_ON_ERROR, COST_VALUE_ON_ERROR, IGNORE_ERROR, ] @@ -98,7 +98,7 @@ def get_run_args_from_yaml(path: str | Path) -> dict: f"provided via run_args." f"See here all valid arguments:" f" {', '.join(expected_parameters)}, " - f"'run_pipeline', 'preload_hooks', 'pipeline_space'" + f"'evaluate_pipeline', 'preload_hooks', 'pipeline_space'" ) # Process complex configurations (e.g., 'pipeline_space', 'searcher') and integrate @@ -146,7 +146,7 @@ def config_loader(path: str | Path) -> 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.'run_pipeline') are also extracted if present + Special keys (e.g.'evaluate_pipeline') are also extracted if present and their corresponding values (dict) at any level in the nested structure. Args: @@ -159,7 +159,7 @@ def extract_leaf_keys(d: dict, special_keys: dict | None = None) -> tuple[dict, """ if special_keys is None: special_keys = { - RUN_PIPELINE: None, + EVALUATE_PIPELINE: None, PRE_LOAD_HOOKS: None, SEARCHER: None, PIPELINE_SPACE: None, @@ -194,7 +194,7 @@ def handle_special_argument_cases(settings: dict, special_configs: dict) -> None """ # process special configs - process_run_pipeline(RUN_PIPELINE, special_configs, settings) + process_evaluate_pipeline(EVALUATE_PIPELINE, special_configs, settings) process_pipeline_space(PIPELINE_SPACE, special_configs, settings) process_searcher(SEARCHER, special_configs, settings) @@ -284,7 +284,7 @@ def process_searcher(key: str, special_configs: dict, settings: dict) -> None: settings[key] = searcher -def process_run_pipeline(key: str, special_configs: dict, settings: dict) -> None: +def process_evaluate_pipeline(key: str, special_configs: dict, settings: dict) -> None: """Processes the run pipeline configuration and updates the settings dictionary. Args: @@ -405,7 +405,7 @@ def check_run_args(settings: dict) -> None: # [task_id, development_stage_id, pre_load_hooks] require special handling of type, # that's why they are not listed expected_types = { - RUN_PIPELINE: Callable, + EVALUATE_PIPELINE: Callable, ROOT_DIRECTORY: str, # TODO: Support CS.ConfigurationSpace for pipeline_space PIPELINE_SPACE: (str, dict), @@ -415,7 +415,7 @@ def check_run_args(settings: dict) -> None: MAX_COST_TOTAL: (int, float), MAX_EVALUATIONS_PER_RUN: int, CONTINUE_UNTIL_MAX_EVALUATION_COMPLETED: bool, - LOSS_VALUE_ON_ERROR: float, + OBJECTIVE_TO_MINIMIZE_VALUE_ON_ERROR: float, COST_VALUE_ON_ERROR: float, IGNORE_ERROR: bool, SEARCHER_KWARGS: dict, @@ -447,7 +447,7 @@ def check_run_args(settings: dict) -> None: def check_essential_arguments( - run_pipeline: Callable | None, + evaluate_pipeline: Callable | None, root_directory: str | None, pipeline_space: dict | None, max_cost_total: int | None, @@ -456,13 +456,13 @@ def check_essential_arguments( ) -> None: """Validates essential NePS configuration arguments. - Ensures 'run_pipeline', 'root_directory', 'pipeline_space', and either + Ensures 'evaluate_pipeline', 'root_directory', 'pipeline_space', and either 'max_cost_total' or 'max_evaluations_total' are provided for NePS execution. Raises ValueError with missing argument details. Additionally, checks 'searcher' is a BaseOptimizer if 'pipeline_space' is absent. Args: - run_pipeline: Function for the pipeline execution. + evaluate_pipeline: Function for the pipeline execution. root_directory (str): Directory path for data storage. pipeline_space: search space for this run. max_cost_total: Max allowed total cost for experiments. @@ -472,8 +472,8 @@ def check_essential_arguments( Raises: ValueError: Missing or invalid essential arguments. """ - if not run_pipeline: - raise ValueError("'run_pipeline' is required but was not provided.") + if not evaluate_pipeline: + raise ValueError("'evaluate_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 and not isinstance(searcher, BaseOptimizer): @@ -515,7 +515,7 @@ def __init__(self, func_args: dict, yaml_args: Path | str | Default | None = Non func_args (dict): The function arguments directly passed to NePS. yaml_args (dict | None): Optional. YAML file arguments provided via run_args. """ - self.run_pipeline = UNSET + self.evaluate_pipeline = UNSET self.root_directory = UNSET self.pipeline_space = UNSET self.overwrite_working_directory = UNSET @@ -527,7 +527,7 @@ def __init__(self, func_args: dict, yaml_args: Path | str | Default | None = Non self.continue_until_max_evaluation_completed = UNSET self.max_cost_total = UNSET self.ignore_errors = UNSET - self.loss_value_on_error = UNSET + self.objective_to_minimize_value_on_error = UNSET self.cost_value_on_error = UNSET self.pre_load_hooks = UNSET self.searcher = UNSET @@ -598,7 +598,7 @@ def check(self) -> None: f"{', '.join(unassigned_attributes)}" ) check_essential_arguments( - self.run_pipeline, # type: ignore + self.evaluate_pipeline, # type: ignore self.root_directory, # type: ignore self.pipeline_space, # type: ignore self.max_cost_total, # type: ignore diff --git a/neps/utils/types.py b/neps/utils/types.py index f57762a5..e5fd343a 100644 --- a/neps/utils/types.py +++ b/neps/utils/types.py @@ -68,7 +68,7 @@ class _ConfigResultForStats: metadata: dict @property - def loss(self) -> float | ERROR: + def objective_to_minimize(self) -> float | ERROR: if isinstance(self.result, dict): - return float(self.result["loss"]) + return float(self.result["objective_to_minimize"]) return "error" diff --git a/neps_examples/basic_usage/hpo_usage_example.py b/neps_examples/basic_usage/hpo_usage_example.py index f1c58573..5469f879 100644 --- a/neps_examples/basic_usage/hpo_usage_example.py +++ b/neps_examples/basic_usage/hpo_usage_example.py @@ -1,5 +1,6 @@ import logging import time +from warnings import warn import numpy as np @@ -12,22 +13,40 @@ def run_pipeline( categorical_name2, integer_name1, integer_name2, +): + warn("run_pipeline is deprecated, use evaluate_pipeline instead", DeprecationWarning) + return evaluate_pipeline( + float_name1, + float_name2, + categorical_name1, + categorical_name2, + integer_name1, + integer_name2, + ) + +def evaluate_pipeline( + float_name1, + float_name2, + categorical_name1, + categorical_name2, + integer_name1, + integer_name2, ): # neps optimize to find values that maximizes sum, for demonstration only - loss = -float( + objective_to_minimize = -float( np.sum( [float_name1, float_name2, categorical_name1, integer_name1, integer_name2] ) ) if categorical_name2 == "a": - loss += 1 + objective_to_minimize += 1 - return loss + return objective_to_minimize logging.basicConfig(level=logging.INFO) neps.run( - run_pipeline=run_pipeline, + evaluate_pipeline=evaluate_pipeline, pipeline_space="search_space_example.yaml", root_directory="results/hyperparameters_example", post_run_summary=True, diff --git a/neps_examples/basic_usage/hyperparameters.py b/neps_examples/basic_usage/hyperparameters.py index 724974ae..f86a5ae4 100644 --- a/neps_examples/basic_usage/hyperparameters.py +++ b/neps_examples/basic_usage/hyperparameters.py @@ -1,15 +1,19 @@ import logging import time +from warnings import warn import numpy as np import neps +def evaluate_pipeline(float1, float2, categorical, integer1, integer2): + warn("run_pipeline is deprecated, use evaluate_pipeline instead", DeprecationWarning) + return evaluate_pipeline(float1, float2, categorical, integer1, integer2) -def run_pipeline(float1, float2, categorical, integer1, integer2): - loss = -float(np.sum([float1, float2, int(categorical), integer1, integer2])) +def evaluate_pipeline(float1, float2, categorical, integer1, integer2): + objective_to_minimize = -float(np.sum([float1, float2, int(categorical), integer1, integer2])) # time.sleep(0.7) # For demonstration purposes - return loss + return objective_to_minimize pipeline_space = dict( @@ -22,7 +26,7 @@ def run_pipeline(float1, float2, categorical, integer1, integer2): logging.basicConfig(level=logging.INFO) neps.run( - run_pipeline=run_pipeline, + evaluate_pipeline=evaluate_pipeline, pipeline_space=pipeline_space, root_directory="results/hyperparameters_example", post_run_summary=True, diff --git a/neps_examples/convenience/logging_additional_info.py b/neps_examples/convenience/logging_additional_info.py index 34470d9f..70b2681c 100644 --- a/neps_examples/convenience/logging_additional_info.py +++ b/neps_examples/convenience/logging_additional_info.py @@ -1,17 +1,21 @@ import logging import time +from warnings import warn import numpy as np import neps - def run_pipeline(float1, float2, categorical, integer1, integer2): + warn("run_pipeline is deprecated, use evaluate_pipeline instead", DeprecationWarning) + return evaluate_pipeline(float1, float2, categorical, integer1, integer2) + +def evaluate_pipeline(float1, float2, categorical, integer1, integer2): start = time.time() - loss = -float(np.sum([float1, float2, int(categorical), integer1, integer2])) + objective_to_minimize = -float(np.sum([float1, float2, int(categorical), integer1, integer2])) end = time.time() return { - "loss": loss, + "objective_to_minimize": objective_to_minimize, "info_dict": { # Optionally include additional information as an info_dict "train_time": end - start, }, @@ -28,7 +32,7 @@ def run_pipeline(float1, float2, categorical, integer1, integer2): logging.basicConfig(level=logging.INFO) neps.run( - run_pipeline=run_pipeline, + evaluate_pipeline=evaluate_pipeline, pipeline_space=pipeline_space, root_directory="results/logging_additional_info", max_evaluations_total=5, diff --git a/neps_examples/convenience/neps_tblogger_tutorial.py b/neps_examples/convenience/neps_tblogger_tutorial.py index 1a363e60..f3fd8322 100644 --- a/neps_examples/convenience/neps_tblogger_tutorial.py +++ b/neps_examples/convenience/neps_tblogger_tutorial.py @@ -51,6 +51,7 @@ import random import time from typing import Tuple +from warnings import warn import numpy as np import torch @@ -155,7 +156,7 @@ def forward(self, x: torch.Tensor): # misclassified images. -def loss_ev(model: nn.Module, data_loader: DataLoader) -> float: +def objective_to_minimize_ev(model: nn.Module, data_loader: DataLoader) -> float: # Set the model in evaluation mode (no gradient computation). model.eval() @@ -208,22 +209,22 @@ def training( for x, y in train_loader: optimizer.zero_grad() output = model(x) - loss = criterion(output, y) - loss.backward() + objective_to_minimize = criterion(output, y) + objective_to_minimize.backward() optimizer.step() predicted_labels = torch.argmax(output, dim=1) incorrect_mask = predicted_labels != y incorrect_images.append(x[incorrect_mask]) - # Calculate validation loss using the loss_ev function. - validation_loss = loss_ev(model, validation_loader) + # Calculate validation objective_to_minimize using the objective_to_minimize_ev function. + validation_objective_to_minimize = objective_to_minimize_ev(model, validation_loader) # Return the misclassified image by during model training. if len(incorrect_images) > 0: incorrect_images = torch.cat(incorrect_images, dim=0) - return (validation_loss, incorrect_images) + return (validation_objective_to_minimize, incorrect_images) ############################################################# @@ -243,8 +244,13 @@ def pipeline_space() -> dict: ############################################################# # Implement the pipeline run search. - def run_pipeline(lr, optim, weight_decay): + # Deprecated function, use evaluate_pipeline instead + warn("run_pipeline is deprecated, use evaluate_pipeline instead", DeprecationWarning) + return evaluate_pipeline(lr, optim, weight_decay) + + +def evaluate_pipeline(lr, optim, weight_decay): # Create the network model. model = MLP() @@ -268,7 +274,7 @@ def run_pipeline(lr, optim, weight_decay): criterion = nn.CrossEntropyLoss() for i in range(max_epochs): - loss, miss_img = training( + objective_to_minimize, miss_img = training( optimizer=optimizer, model=model, criterion=criterion, @@ -295,10 +301,10 @@ def run_pipeline(lr, optim, weight_decay): # 4. First two layer gradients passed as scalar configs. tblogger.log( - loss=loss, + objective_to_minimize=objective_to_minimize, current_epoch=i, write_summary_incumbent=False, # Set to `True` for a live incumbent trajectory. - writer_config_scalar=True, # Set to `True` for a live loss trajectory for each config. + writer_config_scalar=True, # Set to `True` for a live objective_to_minimize trajectory for each config. writer_config_hparam=True, # Set to `True` for live parallel coordinate, scatter plot matrix, and table view. # Appending extra data extra_data={ @@ -313,15 +319,15 @@ def run_pipeline(lr, optim, weight_decay): scheduler.step() - print(f" Epoch {i + 1} / {max_epochs} Val Error: {loss} ") + print(f" Epoch {i + 1} / {max_epochs} Val Error: {objective_to_minimize} ") # Calculate training and test accuracy. - train_accuracy = loss_ev(model, train_loader) - test_accuracy = loss_ev(model, test_loader) + train_accuracy = objective_to_minimize_ev(model, train_loader) + test_accuracy = objective_to_minimize_ev(model, test_loader) # Return a dictionary with relevant metrics and information. return { - "loss": loss, + "objective_to_minimize": objective_to_minimize, "info_dict": { "train_accuracy": train_accuracy, "test_accuracy": test_accuracy, @@ -351,7 +357,7 @@ def run_pipeline(lr, optim, weight_decay): # tblogger.get_status() run_args = dict( - run_pipeline=run_pipeline, + evaluate_pipeline=evaluate_pipeline, pipeline_space=pipeline_space(), root_directory="results/neps_tblogger_example", searcher="random_search", diff --git a/neps_examples/convenience/neps_x_lightning.py b/neps_examples/convenience/neps_x_lightning.py index a8c873c0..8e019957 100644 --- a/neps_examples/convenience/neps_x_lightning.py +++ b/neps_examples/convenience/neps_x_lightning.py @@ -1,363 +1,373 @@ -""" -Exploring NePS Compatibility with PyTorch Lightning -======================================================= - -1. Introduction: ----------------- -Welcome to this tutorial on utilizing NePS-generated files and directories -in conjunction with PyTorch Lightning. - -2. Setup: ---------- -Ensure you have the necessary dependencies installed. You can install the 'NePS' -package by executing the following command: - -```bash -pip install neural-pipeline-search -``` - -Additionally, note that 'NePS' does not include 'torchvision' as a dependency. -You can install it with this command: - -```bash -pip install torchvision==0.14 -``` - -Make sure to download the torchvision version that is compatible with your -pytorch version. More info on this link: - -https://pypi.org/project/torchvision/ - -Additionally, you will need to install the PyTorch Lightning package. This can -be achieved with the following command: - -```bash -pip install lightning -``` - -These dependencies ensure you have everything you need for this tutorial. -""" -import argparse -import glob -import logging -import random -import time -from pathlib import Path -from typing import Tuple - -import lightning as L -import numpy as np -import torch -import torch.nn as nn -import torch.nn.functional as F -from lightning.pytorch.callbacks import ModelCheckpoint -from lightning.pytorch.loggers import TensorBoardLogger -from torch.utils.data import SubsetRandomSampler -from torch.utils.data.dataloader import DataLoader -from torchmetrics import Accuracy -from torchvision.datasets import MNIST -from torchvision.transforms import transforms - -import neps -from neps.utils.common import get_initial_directory, load_lightning_checkpoint - -############################################################# -# Definig the seeds for reproducibility - - -def set_seed(seed=123): - torch.manual_seed(seed) - np.random.seed(seed) - random.seed(seed) - - -############################################################# -# Define the lightning model - - -class LitMNIST(L.LightningModule): - def __init__( - self, - configuration: dict, - n_train: int = 8192, - n_valid: int = 1024, - ): - super().__init__() - - # Initialize the model's hyperparameters with the configuration - self.save_hyperparameters(configuration) - - self.n_train = n_train - self.n_valid = n_valid - - # Define data transformation and loss function - self.transform = transforms.ToTensor() - self.criterion = nn.NLLLoss() - - # Define the model's architecture - self.linear1 = nn.Linear(in_features=784, out_features=392) - self.linear2 = nn.Linear(in_features=392, out_features=196) - self.linear3 = nn.Linear(in_features=196, out_features=10) - - # Define PyTorch Lightning metrics for training, validation, and testing - metric = Accuracy(task="multiclass", num_classes=10) - self.train_accuracy = metric.clone() - self.val_accuracy = metric.clone() - self.test_accuracy = metric.clone() - - def forward(self, x: torch.Tensor) -> torch.Tensor: - # Forward pass function - x = x.view(x.size(0), -1) - x = F.relu(self.linear1(x)) - x = F.relu(self.linear2(x)) - x = self.linear3(x) - - return F.log_softmax(x, dim=1) - - def common_step( - self, batch: Tuple[torch.Tensor, torch.Tensor], batch_idx: int - ) -> Tuple[torch.Tensor, torch.Tensor, torch.Tensor]: - """ - Perform a forward pass and compute loss, predictions, and get the ground - truth labels for a batch of data. - """ - x, y = batch - logits = self.forward(x) - loss = self.criterion(logits, y) - preds = torch.argmax(logits, dim=1) - - return loss, preds, y - - def training_step( - self, batch: Tuple[torch.Tensor, torch.Tensor], batch_idx: int - ) -> float: - loss, preds, y = self.common_step(batch, batch_idx) - self.train_accuracy.update(preds, y) - - self.log_dict( - {"train_loss": loss, "train_acc": self.val_accuracy.compute()}, - on_epoch=True, - on_step=False, - prog_bar=True, - ) - - return loss - - def validation_step( - self, batch: Tuple[torch.Tensor, torch.Tensor], batch_idx: int - ) -> None: - loss, preds, y = self.common_step(batch, batch_idx) - self.val_accuracy.update(preds, y) - - self.log_dict( - {"val_loss": loss, "val_acc": self.val_accuracy.compute()}, - on_epoch=True, - on_step=False, - prog_bar=True, - ) - - def test_step( - self, batch: Tuple[torch.Tensor, torch.Tensor], batch_idx: int - ) -> None: - _, preds, y = self.common_step(batch, batch_idx) - self.test_accuracy.update(preds, y) - - self.log(name="test_acc", value=self.test_accuracy.compute()) - - def configure_optimizers(self) -> torch.optim.Optimizer: - # Configure and return the optimizer based on the configuration - if self.hparams.optimizer == "Adam": - optimizer = torch.optim.Adam( - self.parameters(), - lr=self.hparams.lr, - weight_decay=self.hparams.weight_decay, - ) - elif self.hparams.optimizer == "SGD": - optimizer = torch.optim.SGD( - self.parameters(), - lr=self.hparams.lr, - weight_decay=self.hparams.weight_decay, - ) - else: - raise ValueError( - "The optimizer choices is not one of the available optimizers" - ) - return optimizer - - def on_train_end(self): - # Get the metric at the end of the training and log it with respect to - # it's hyperparameters - val_acc_metric = { - "val_accuracy": self.val_accuracy.compute(), - } - - # Log hyperparameters - self.logger.log_hyperparams(self.hparams, metrics=val_acc_metric) - - def prepare_data(self) -> None: - # Downloading the dataste if not already downloaded - MNIST(self.hparams.data_dir, train=True, download=True) - MNIST(self.hparams.data_dir, train=False, download=True) - - def setup(self, stage: str = None) -> None: - # Assign train/val datasets for use in dataloaders - if stage == "fit" or stage is None: - self.mnist_full = MNIST( - self.hparams.data_dir, train=True, transform=self.transform - ) - - # Create random subsets of the training dataset for validation. - self.train_sampler = SubsetRandomSampler(range(self.n_train)) - self.val_sampler = SubsetRandomSampler( - range(self.n_train, self.n_train + self.n_valid) - ) - - # Assign test dataset for use in dataloader - if stage == "test" or stage is None: - self.mnist_test = MNIST( - self.hparams.data_dir, train=False, transform=self.transform - ) - - def train_dataloader(self) -> DataLoader: - return DataLoader( - self.mnist_full, - batch_size=self.hparams.batch_size, - sampler=self.train_sampler, - num_workers=16, - ) - - def val_dataloader(self) -> DataLoader: - return DataLoader( - self.mnist_full, - batch_size=self.hparams.batch_size, - sampler=self.val_sampler, - num_workers=16, - ) - - def test_dataloader(self) -> DataLoader: - return DataLoader( - self.mnist_test, - batch_size=self.hparams.batch_size, - num_workers=16, - ) - - -############################################################# -# Define search space - - -def search_space() -> dict: - # Define a dictionary to represent the hyperparameter search space - space = dict( - data_dir=neps.Constant("./data"), - batch_size=neps.Constant(64), - lr=neps.Float(lower=1e-5, upper=1e-2, log=True, default=1e-3), - weight_decay=neps.Float( - lower=1e-5, upper=1e-3, log=True, default=5e-4 - ), - optimizer=neps.Categorical(choices=["Adam", "SGD"], default="Adam"), - epochs=neps.Integer(lower=1, upper=9, log=False, is_fidelity=True), - ) - return space - - -############################################################# -# Define the run pipeline function - - -def run_pipeline(pipeline_directory, previous_pipeline_directory, **config) -> dict: - # Initialize the first directory to store the event and checkpoints files - init_dir = get_initial_directory(pipeline_directory) - checkpoint_dir = init_dir / "checkpoints" - - # Initialize the model and checkpoint dir - model = LitMNIST(config) - - # Create the TensorBoard logger for logging - logger = TensorBoardLogger( - save_dir=init_dir, name="data", version="logs", default_hp_metric=False - ) - - # Add checkpoints at the end of training - checkpoint_callback = ModelCheckpoint( - dirpath=checkpoint_dir, - filename="{epoch}-{val_loss:.2f}", - ) - - # Use this function to load the previous checkpoint if it exists - checkpoint_path, checkpoint = load_lightning_checkpoint( - previous_pipeline_directory=previous_pipeline_directory, - checkpoint_dir=checkpoint_dir, - ) - - if checkpoint is None: - previously_spent_epochs = 0 - else: - previously_spent_epochs = checkpoint["epoch"] - - # Create a PyTorch Lightning Trainer - epochs = config["epochs"] - - trainer = L.Trainer( - logger=logger, - max_epochs=epochs, - callbacks=[checkpoint_callback], - ) - - # Train the model and retrieve training/validation metrics - if checkpoint_path: - trainer.fit(model, ckpt_path=checkpoint_path) - else: - trainer.fit(model) - - train_accuracy = trainer.logged_metrics.get("train_acc", None) - val_loss = trainer.logged_metrics.get("val_loss", None) - val_accuracy = trainer.logged_metrics.get("val_acc", None) - - # Test the model and retrieve test metrics - trainer.test(model) - - test_accuracy = trainer.logged_metrics.get("test_acc", None) - - return { - "loss": val_loss, - "cost": epochs - previously_spent_epochs, - "info_dict": { - "train_accuracy": train_accuracy, - "val_accuracy": val_accuracy, - "test_accuracy": test_accuracy, - }, - } - - -if __name__ == "__main__": - # Parse command line arguments - parser = argparse.ArgumentParser() - parser.add_argument( - "--max_evaluations_total", - type=int, - default=15, - help="Number of different configurations to train", - ) - args = parser.parse_args() - - # Initialize the logger and record start time - start_time = time.time() - set_seed(112) - logging.basicConfig(level=logging.INFO) - - # Run NePS with specified parameters - neps.run( - run_pipeline=run_pipeline, - pipeline_space=search_space(), - root_directory="results/hyperband", - max_evaluations_total=args.max_evaluations_total, - searcher="hyperband", - ) - - # Record the end time and calculate execution time - end_time = time.time() - execution_time = end_time - start_time - - # Log the execution time - logging.info(f"Execution time: {execution_time} seconds") +""" +Exploring NePS Compatibility with PyTorch Lightning +======================================================= + +1. Introduction: +---------------- +Welcome to this tutorial on utilizing NePS-generated files and directories +in conjunction with PyTorch Lightning. + +2. Setup: +--------- +Ensure you have the necessary dependencies installed. You can install the 'NePS' +package by executing the following command: + +```bash +pip install neural-pipeline-search +``` + +Additionally, note that 'NePS' does not include 'torchvision' as a dependency. +You can install it with this command: + +```bash +pip install torchvision==0.14 +``` + +Make sure to download the torchvision version that is compatible with your +pytorch version. More info on this link: + +https://pypi.org/project/torchvision/ + +Additionally, you will need to install the PyTorch Lightning package. This can +be achieved with the following command: + +```bash +pip install lightning +``` + +These dependencies ensure you have everything you need for this tutorial. +""" +import argparse +import glob +import logging +import random +import time +from pathlib import Path +from typing import Tuple +from warnings import warn + +import lightning as L +import numpy as np +import torch +import torch.nn as nn +import torch.nn.functional as F +from lightning.pytorch.callbacks import ModelCheckpoint +from lightning.pytorch.loggers import TensorBoardLogger +from torch.utils.data import SubsetRandomSampler +from torch.utils.data.dataloader import DataLoader +from torchmetrics import Accuracy +from torchvision.datasets import MNIST +from torchvision.transforms import transforms + +import neps +from neps.utils.common import get_initial_directory, load_lightning_checkpoint + +############################################################# +# Definig the seeds for reproducibility + + +def set_seed(seed=123): + torch.manual_seed(seed) + np.random.seed(seed) + random.seed(seed) + + +############################################################# +# Define the lightning model + + +class LitMNIST(L.LightningModule): + def __init__( + self, + configuration: dict, + n_train: int = 8192, + n_valid: int = 1024, + ): + super().__init__() + + # Initialize the model's hyperparameters with the configuration + self.save_hyperparameters(configuration) + + self.n_train = n_train + self.n_valid = n_valid + + # Define data transformation and objective_to_minimize function + self.transform = transforms.ToTensor() + self.criterion = nn.NLLLoss() + + # Define the model's architecture + self.linear1 = nn.Linear(in_features=784, out_features=392) + self.linear2 = nn.Linear(in_features=392, out_features=196) + self.linear3 = nn.Linear(in_features=196, out_features=10) + + # Define PyTorch Lightning metrics for training, validation, and testing + metric = Accuracy(task="multiclass", num_classes=10) + self.train_accuracy = metric.clone() + self.val_accuracy = metric.clone() + self.test_accuracy = metric.clone() + + def forward(self, x: torch.Tensor) -> torch.Tensor: + # Forward pass function + x = x.view(x.size(0), -1) + x = F.relu(self.linear1(x)) + x = F.relu(self.linear2(x)) + x = self.linear3(x) + + return F.log_softmax(x, dim=1) + + def common_step( + self, batch: Tuple[torch.Tensor, torch.Tensor], batch_idx: int + ) -> Tuple[torch.Tensor, torch.Tensor, torch.Tensor]: + """ + Perform a forward pass and compute objective_to_minimize, predictions, and get the ground + truth labels for a batch of data. + """ + x, y = batch + logits = self.forward(x) + objective_to_minimize = self.criterion(logits, y) + preds = torch.argmax(logits, dim=1) + + return objective_to_minimize, preds, y + + def training_step( + self, batch: Tuple[torch.Tensor, torch.Tensor], batch_idx: int + ) -> float: + objective_to_minimize, preds, y = self.common_step(batch, batch_idx) + self.train_accuracy.update(preds, y) + + self.log_dict( + {"train_objective_to_minimize": objective_to_minimize, "train_acc": self.val_accuracy.compute()}, + on_epoch=True, + on_step=False, + prog_bar=True, + ) + + return objective_to_minimize + + def validation_step( + self, batch: Tuple[torch.Tensor, torch.Tensor], batch_idx: int + ) -> None: + objective_to_minimize, preds, y = self.common_step(batch, batch_idx) + self.val_accuracy.update(preds, y) + + self.log_dict( + {"val_objective_to_minimize": objective_to_minimize, "val_acc": self.val_accuracy.compute()}, + on_epoch=True, + on_step=False, + prog_bar=True, + ) + + def test_step( + self, batch: Tuple[torch.Tensor, torch.Tensor], batch_idx: int + ) -> None: + _, preds, y = self.common_step(batch, batch_idx) + self.test_accuracy.update(preds, y) + + self.log(name="test_acc", value=self.test_accuracy.compute()) + + def configure_optimizers(self) -> torch.optim.Optimizer: + # Configure and return the optimizer based on the configuration + if self.hparams.optimizer == "Adam": + optimizer = torch.optim.Adam( + self.parameters(), + lr=self.hparams.lr, + weight_decay=self.hparams.weight_decay, + ) + elif self.hparams.optimizer == "SGD": + optimizer = torch.optim.SGD( + self.parameters(), + lr=self.hparams.lr, + weight_decay=self.hparams.weight_decay, + ) + else: + raise ValueError( + "The optimizer choices is not one of the available optimizers" + ) + return optimizer + + def on_train_end(self): + # Get the metric at the end of the training and log it with respect to + # it's hyperparameters + val_acc_metric = { + "val_accuracy": self.val_accuracy.compute(), + } + + # Log hyperparameters + self.logger.log_hyperparams(self.hparams, metrics=val_acc_metric) + + def prepare_data(self) -> None: + # Downloading the dataste if not already downloaded + MNIST(self.hparams.data_dir, train=True, download=True) + MNIST(self.hparams.data_dir, train=False, download=True) + + def setup(self, stage: str = None) -> None: + # Assign train/val datasets for use in dataloaders + if stage == "fit" or stage is None: + self.mnist_full = MNIST( + self.hparams.data_dir, train=True, transform=self.transform + ) + + # Create random subsets of the training dataset for validation. + self.train_sampler = SubsetRandomSampler(range(self.n_train)) + self.val_sampler = SubsetRandomSampler( + range(self.n_train, self.n_train + self.n_valid) + ) + + # Assign test dataset for use in dataloader + if stage == "test" or stage is None: + self.mnist_test = MNIST( + self.hparams.data_dir, train=False, transform=self.transform + ) + + def train_dataloader(self) -> DataLoader: + return DataLoader( + self.mnist_full, + batch_size=self.hparams.batch_size, + sampler=self.train_sampler, + num_workers=16, + ) + + def val_dataloader(self) -> DataLoader: + return DataLoader( + self.mnist_full, + batch_size=self.hparams.batch_size, + sampler=self.val_sampler, + num_workers=16, + ) + + def test_dataloader(self) -> DataLoader: + return DataLoader( + self.mnist_test, + batch_size=self.hparams.batch_size, + num_workers=16, + ) + + +############################################################# +# Define search space + + +def search_space() -> dict: + # Define a dictionary to represent the hyperparameter search space + space = dict( + data_dir=neps.Constant("./data"), + batch_size=neps.Constant(64), + lr=neps.Float(lower=1e-5, upper=1e-2, log=True, prior=1e-3), + weight_decay=neps.Float( + lower=1e-5, upper=1e-3, log=True, prior=5e-4 + ), + optimizer=neps.Categorical(choices=["Adam", "SGD"], prior="Adam"), + epochs=neps.Integer(lower=1, upper=9, log=False, is_fidelity=True), + ) + return space + + +############################################################# +# Define the run pipeline function + +def run_pipeline(pipeline_directory, previous_pipeline_directory, **config): + # Deprecated function, use evaluate_pipeline instead + warn("run_pipeline is deprecated, use evaluate_pipeline instead", DeprecationWarning) + return evaluate_pipeline( + pipeline_directory, + previous_pipeline_directory, + **config, + ) + + +def evaluate_pipeline(pipeline_directory, previous_pipeline_directory, **config) -> dict: + # Initialize the first directory to store the event and checkpoints files + init_dir = get_initial_directory(pipeline_directory) + checkpoint_dir = init_dir / "checkpoints" + + # Initialize the model and checkpoint dir + model = LitMNIST(config) + + # Create the TensorBoard logger for logging + logger = TensorBoardLogger( + save_dir=init_dir, name="data", version="logs", default_hp_metric=False + ) + + # Add checkpoints at the end of training + checkpoint_callback = ModelCheckpoint( + dirpath=checkpoint_dir, + filename="{epoch}-{val_objective_to_minimize:.2f}", + ) + + # Use this function to load the previous checkpoint if it exists + checkpoint_path, checkpoint = load_lightning_checkpoint( + previous_pipeline_directory=previous_pipeline_directory, + checkpoint_dir=checkpoint_dir, + ) + + if checkpoint is None: + previously_spent_epochs = 0 + else: + previously_spent_epochs = checkpoint["epoch"] + + # Create a PyTorch Lightning Trainer + epochs = config["epochs"] + + trainer = L.Trainer( + logger=logger, + max_epochs=epochs, + callbacks=[checkpoint_callback], + ) + + # Train the model and retrieve training/validation metrics + if checkpoint_path: + trainer.fit(model, ckpt_path=checkpoint_path) + else: + trainer.fit(model) + + train_accuracy = trainer.logged_metrics.get("train_acc", None) + val_objective_to_minimize = trainer.logged_metrics.get("val_objective_to_minimize", None) + val_accuracy = trainer.logged_metrics.get("val_acc", None) + + # Test the model and retrieve test metrics + trainer.test(model) + + test_accuracy = trainer.logged_metrics.get("test_acc", None) + + return { + "objective_to_minimize": val_objective_to_minimize, + "cost": epochs - previously_spent_epochs, + "info_dict": { + "train_accuracy": train_accuracy, + "val_accuracy": val_accuracy, + "test_accuracy": test_accuracy, + }, + } + + +if __name__ == "__main__": + # Parse command line arguments + parser = argparse.ArgumentParser() + parser.add_argument( + "--max_evaluations_total", + type=int, + default=15, + help="Number of different configurations to train", + ) + args = parser.parse_args() + + # Initialize the logger and record start time + start_time = time.time() + set_seed(112) + logging.basicConfig(level=logging.INFO) + + # Run NePS with specified parameters + neps.run( + evaluate_pipeline=evaluate_pipeline, + pipeline_space=search_space(), + root_directory="results/hyperband", + max_evaluations_total=args.max_evaluations_total, + searcher="hyperband", + ) + + # Record the end time and calculate execution time + end_time = time.time() + execution_time = end_time - start_time + + # Log the execution time + logging.info(f"Execution time: {execution_time} seconds") diff --git a/neps_examples/convenience/running_on_slurm_scripts.py b/neps_examples/convenience/running_on_slurm_scripts.py index 03a73c96..7a52c21b 100644 --- a/neps_examples/convenience/running_on_slurm_scripts.py +++ b/neps_examples/convenience/running_on_slurm_scripts.py @@ -5,6 +5,7 @@ import os import time from pathlib import Path +from warnings import warn import neps @@ -27,9 +28,14 @@ def _get_validation_error(pipeline_directory: Path): return float(validation_error_file.read_text()) return None - def run_pipeline_via_slurm( pipeline_directory: Path, optimizer: str, learning_rate: float +): + warn("run_pipeline_via_slurm is deprecated, use evaluate_pipeline_via_slurm instead", DeprecationWarning) + return evaluate_pipeline_via_slurm(pipeline_directory, optimizer, learning_rate) + +def evaluate_pipeline_via_slurm( + pipeline_directory: Path, optimizer: str, learning_rate: float ): script = f"""#!/bin/bash #SBATCH --time 0-00:05 @@ -58,7 +64,7 @@ def run_pipeline_via_slurm( logging.basicConfig(level=logging.INFO) neps.run( - run_pipeline=run_pipeline_via_slurm, + evaluate_pipeline=evaluate_pipeline_via_slurm, pipeline_space=pipeline_space, root_directory="results/slurm_script_example", max_evaluations_total=5, diff --git a/neps_examples/convenience/working_directory_per_pipeline.py b/neps_examples/convenience/working_directory_per_pipeline.py index 7aa26197..ce36e929 100644 --- a/neps_examples/convenience/working_directory_per_pipeline.py +++ b/neps_examples/convenience/working_directory_per_pipeline.py @@ -1,5 +1,6 @@ import logging from pathlib import Path +from warnings import warn import numpy as np @@ -7,14 +8,18 @@ def run_pipeline(pipeline_directory: Path, float1, categorical, integer1): + warn("run_pipeline is deprecated, use evaluate_pipeline instead", DeprecationWarning) + return evaluate_pipeline(pipeline_directory, float1, categorical, integer1) + +def evaluate_pipeline(pipeline_directory: Path, float1, categorical, integer1): # When adding pipeline_directory to run_pipeline, neps detects its presence and # passes a directory unique for each pipeline configuration. You can then use this # pipeline_directory to create / save files pertaining to a specific pipeline, e.g.: weight_file = pipeline_directory / "weight_file.txt" weight_file.write_text("0") - loss = -float(np.sum([float1, int(categorical), integer1])) - return loss + objective_to_minimize = -float(np.sum([float1, int(categorical), integer1])) + return objective_to_minimize pipeline_space = dict( @@ -25,7 +30,7 @@ def run_pipeline(pipeline_directory: Path, float1, categorical, integer1): logging.basicConfig(level=logging.INFO) neps.run( - run_pipeline=run_pipeline, + evaluate_pipeline=evaluate_pipeline, pipeline_space=pipeline_space, root_directory="results/working_directory_per_pipeline", max_evaluations_total=5, diff --git a/neps_examples/declarative_usage/hpo_example.py b/neps_examples/declarative_usage/hpo_example.py index 8af8af2e..0e599440 100644 --- a/neps_examples/declarative_usage/hpo_example.py +++ b/neps_examples/declarative_usage/hpo_example.py @@ -60,7 +60,7 @@ def training_pipeline(num_layers, num_neurons, epochs, learning_rate, optimizer) optimizer (str): Name of the optimizer to use ('adam' or 'sgd'). Returns: - float: The average loss over the validation set after training. + float: The average objective_to_minimize over the validation set after training. Raises: KeyError: If the specified optimizer is not supported. @@ -101,20 +101,20 @@ def training_pipeline(num_layers, num_neurons, epochs, learning_rate, optimizer) for batch_idx, (data, target) in enumerate(train_loader): optimizer.zero_grad() output = model(data) - loss = criterion(output, target) - loss.backward() + objective_to_minimize = criterion(output, target) + objective_to_minimize.backward() optimizer.step() # Validation loop model.eval() - val_loss = 0 + val_objective_to_minimize = 0 with torch.no_grad(): for data, target in val_loader: output = model(data) - val_loss += criterion(output, target).item() + val_objective_to_minimize += criterion(output, target).item() - val_loss /= len(val_loader.dataset) - return val_loss + val_objective_to_minimize /= len(val_loader.dataset) + return val_objective_to_minimize if __name__ == "__main__": diff --git a/neps_examples/efficiency/expert_priors_for_hyperparameters.py b/neps_examples/efficiency/expert_priors_for_hyperparameters.py index 46ed9325..e85802ba 100644 --- a/neps_examples/efficiency/expert_priors_for_hyperparameters.py +++ b/neps_examples/efficiency/expert_priors_for_hyperparameters.py @@ -1,10 +1,14 @@ import logging import time +from warnings import warn import neps - def run_pipeline(some_float, some_integer, some_cat): + warn("run_pipeline is deprecated, use evaluate_pipeline instead", DeprecationWarning) + return evaluate_pipeline(some_float, some_integer, some_cat) + +def evaluate_pipeline(some_float, some_integer, some_cat): start = time.time() if some_cat != "a": y = some_float + some_integer @@ -12,7 +16,7 @@ def run_pipeline(some_float, some_integer, some_cat): y = -some_float - some_integer end = time.time() return { - "loss": y, + "objective_to_minimize": y, "info_dict": { "test_score": y, "train_time": end - start, @@ -24,19 +28,19 @@ def run_pipeline(some_float, some_integer, some_cat): # that speeds up the search pipeline_space = dict( some_float=neps.Float( - lower=1, upper=1000, log=True, default=900, default_confidence="medium" + lower=1, upper=1000, log=True, prior=900, prior_confidence="medium" ), some_integer=neps.Integer( - lower=0, upper=50, default=35, default_confidence="low" + lower=0, upper=50, prior=35, prior_confidence="low" ), some_cat=neps.Categorical( - choices=["a", "b", "c"], default="a", default_confidence="high" + choices=["a", "b", "c"], prior="a", prior_confidence="high" ), ) logging.basicConfig(level=logging.INFO) neps.run( - run_pipeline=run_pipeline, + evaluate_pipeline=evaluate_pipeline, pipeline_space=pipeline_space, root_directory="results/user_priors_example", max_evaluations_total=15, diff --git a/neps_examples/efficiency/freeze_thaw.py b/neps_examples/efficiency/freeze_thaw.py index 32943ec2..9c63d109 100644 --- a/neps_examples/efficiency/freeze_thaw.py +++ b/neps_examples/efficiency/freeze_thaw.py @@ -48,7 +48,7 @@ def training_pipeline( optimizer (str): Name of the optimizer to use ('adam' or 'sgd'). Returns: - float: The average loss over the validation set after training. + float: The average objective_to_minimize over the validation set after training. Raises: KeyError: If the specified optimizer is not supported. @@ -92,19 +92,19 @@ def training_pipeline( for batch_idx, (data, target) in enumerate(train_loader): optimizer.zero_grad() output = model(data) - loss = criterion(output, target) - loss.backward() + objective_to_minimize = criterion(output, target) + objective_to_minimize.backward() optimizer.step() # Validation loop model.eval() - val_loss = 0 + val_objective_to_minimize = 0 val_correct = 0 val_total = 0 with torch.no_grad(): for data, target in val_loader: output = model(data) - val_loss += criterion(output, target).item() + val_objective_to_minimize += criterion(output, target).item() # Get the predicted class _, predicted = torch.max(output.data, 1) @@ -113,7 +113,7 @@ def training_pipeline( val_total += target.size(0) val_correct += (predicted == target).sum().item() - val_loss /= len(val_loader.dataset) + val_objective_to_minimize /= len(val_loader.dataset) val_err = 1 - val_correct / val_total # Saving checkpoint @@ -126,17 +126,17 @@ def training_pipeline( # Logging tblogger.log( - loss=val_loss, + objective_to_minimize=val_objective_to_minimize, current_epoch=epochs, # Set to `True` for a live incumbent trajectory. write_summary_incumbent=True, - # Set to `True` for a live loss trajectory for each config. + # Set to `True` for a live objective_to_minimize trajectory for each config. writer_config_scalar=True, # Set to `True` for live parallel coordinate, scatter plot matrix, and table view. writer_config_hparam=True, # Appending extra data extra_data={ - "train_loss": tblogger.scalar_logging(loss.item()), + "train_objective_to_minimize": tblogger.scalar_logging(objective_to_minimize.item()), "val_err": tblogger.scalar_logging(val_err), }, ) @@ -157,7 +157,7 @@ def training_pipeline( neps.run( pipeline_space=pipeline_space, - run_pipeline=training_pipeline, + evaluate_pipeline=training_pipeline, searcher="ifbo", max_evaluations_total=50, root_directory="./debug/ifbo-mnist/", diff --git a/neps_examples/efficiency/multi_fidelity.py b/neps_examples/efficiency/multi_fidelity.py index 3e58eefa..8f0eedc6 100644 --- a/neps_examples/efficiency/multi_fidelity.py +++ b/neps_examples/efficiency/multi_fidelity.py @@ -1,4 +1,5 @@ import logging +from warnings import warn import numpy as np import torch @@ -43,6 +44,10 @@ def get_model_and_optimizer(learning_rate): def run_pipeline(pipeline_directory, previous_pipeline_directory, learning_rate, epoch): + warn("run_pipeline is deprecated, use evaluate_pipeline instead", DeprecationWarning) + return evaluate_pipeline(pipeline_directory, previous_pipeline_directory, learning_rate, epoch) + +def evaluate_pipeline(pipeline_directory, previous_pipeline_directory, learning_rate, epoch): model, optimizer = get_model_and_optimizer(learning_rate) checkpoint_name = "checkpoint.pth" @@ -67,9 +72,9 @@ def run_pipeline(pipeline_directory, previous_pipeline_directory, learning_rate, pipeline_directory / checkpoint_name, ) - loss = np.log(learning_rate / epoch) # Replace with actual error + objective_to_minimize = np.log(learning_rate / epoch) # Replace with actual error epochs_spent_in_this_call = epoch - epochs_previously_spent # Optional for stopping - return dict(loss=loss, cost=epochs_spent_in_this_call) + return dict(objective_to_minimize=objective_to_minimize, cost=epochs_spent_in_this_call) pipeline_space = dict( @@ -79,7 +84,7 @@ def run_pipeline(pipeline_directory, previous_pipeline_directory, learning_rate, logging.basicConfig(level=logging.INFO) neps.run( - run_pipeline=run_pipeline, + evaluate_pipeline=evaluate_pipeline, pipeline_space=pipeline_space, root_directory="results/multi_fidelity_example", # Optional: Do not start another evaluation after <=50 epochs, corresponds to cost diff --git a/neps_examples/efficiency/multi_fidelity_and_expert_priors.py b/neps_examples/efficiency/multi_fidelity_and_expert_priors.py index 1de28617..6a7655b0 100644 --- a/neps_examples/efficiency/multi_fidelity_and_expert_priors.py +++ b/neps_examples/efficiency/multi_fidelity_and_expert_priors.py @@ -1,4 +1,5 @@ import logging +from warnings import warn import numpy as np @@ -6,26 +7,30 @@ def run_pipeline(float1, float2, integer1, fidelity): - loss = -float(np.sum([float1, float2, integer1])) / fidelity - return loss + warn("run_pipeline is deprecated, use evaluate_pipeline instead", DeprecationWarning) + return evaluate_pipeline(float1, float2, integer1, fidelity) + +def evaluate_pipeline(float1, float2, integer1, fidelity): + objective_to_minimize = -float(np.sum([float1, float2, integer1])) / fidelity + return objective_to_minimize pipeline_space = dict( float1=neps.Float( - lower=1, upper=1000, log=False, default=600, default_confidence="medium" + lower=1, upper=1000, log=False, prior=600, prior_confidence="medium" ), float2=neps.Float( - lower=-10, upper=10, default=0, default_confidence="medium" + lower=-10, upper=10, prior=0, prior_confidence="medium" ), integer1=neps.Integer( - lower=0, upper=50, default=35, default_confidence="low" + lower=0, upper=50, prior=35, prior_confidence="low" ), fidelity=neps.Integer(lower=1, upper=10, is_fidelity=True), ) logging.basicConfig(level=logging.INFO) neps.run( - run_pipeline=run_pipeline, + evaluate_pipeline=evaluate_pipeline, pipeline_space=pipeline_space, root_directory="results/multifidelity_priors", max_evaluations_total=25, # For an alternate stopping method see multi_fidelity.py diff --git a/neps_examples/experimental/cost_aware.py b/neps_examples/experimental/cost_aware.py index 5b08bdcc..8c7f4d32 100644 --- a/neps_examples/experimental/cost_aware.py +++ b/neps_examples/experimental/cost_aware.py @@ -1,19 +1,24 @@ import logging import time +from warnings import warn import numpy as np import neps -def run_pipeline( - pipeline_directory, float1, float2, categorical, integer1, integer2 +def run_pipeline(float1, float2, categorical, integer1, integer2): + warn("run_pipeline is deprecated, use evaluate_pipeline instead", DeprecationWarning) + return evaluate_pipeline(float1, float2, categorical, integer1, integer2) + +def evaluate_pipeline( + float1, float2, categorical, integer1, integer2 ): start = time.time() y = -float(np.sum([float1, float2, int(categorical), integer1, integer2])) end = time.time() return { - "loss": y, + "objective_to_minimize": y, "cost": (end - start) + float1, } @@ -21,7 +26,7 @@ def run_pipeline( pipeline_space = dict( float1=neps.Float(lower=0, upper=1, log=False), float2=neps.Float( - lower=0, upper=10, log=False, default=10, default_confidence="medium" + lower=0, upper=10, log=False, prior=10, prior_confidence="medium" ), categorical=neps.Categorical(choices=[0, 1]), integer1=neps.Integer(lower=0, upper=1, log=False), @@ -30,12 +35,12 @@ def run_pipeline( logging.basicConfig(level=logging.INFO) neps.run( - run_pipeline=run_pipeline, + evaluate_pipeline=evaluate_pipeline, pipeline_space=pipeline_space, root_directory="results/cost_aware_example", searcher="cost_cooling", max_evaluations_total=12, # TODO(Jan): remove initial_design_size=5, - budget=100, + max_cost_total=100, ) previous_results, pending_configs = neps.status("results/cost_aware_example") diff --git a/neps_examples/experimental/expert_priors_for_architecture_and_hyperparameters.py b/neps_examples/experimental/expert_priors_for_architecture_and_hyperparameters.py index c0d1f5f6..073f6992 100644 --- a/neps_examples/experimental/expert_priors_for_architecture_and_hyperparameters.py +++ b/neps_examples/experimental/expert_priors_for_architecture_and_hyperparameters.py @@ -1,5 +1,6 @@ import logging import time +from warnings import warn from torch import nn @@ -70,6 +71,10 @@ def set_recursive_attribute(op_name, predecessor_values): def run_pipeline(some_architecture, some_float, some_integer, some_cat): + warn("run_pipeline is deprecated, use evaluate_pipeline instead", DeprecationWarning) + return evaluate_pipeline(some_architecture, some_float, some_integer, some_cat) + +def evaluate_pipeline(some_architecture, some_float, some_integer, some_cat): start = time.time() in_channels = 3 @@ -97,7 +102,7 @@ def run_pipeline(some_architecture, some_float, some_integer, some_cat): end = time.time() return { - "loss": y, + "objective_to_minimize": y, "info_dict": { "test_score": y, "train_time": end - start, @@ -114,19 +119,19 @@ def run_pipeline(some_architecture, some_float, some_integer, some_cat): prior=prior_distr, ), some_float=neps.Float( - lower=1, upper=1000, log=True, default=900, default_confidence="medium" + lower=1, upper=1000, log=True, prior=900, prior_confidence="medium" ), some_integer=neps.Integer( - lower=0, upper=50, default=35, default_confidence="low" + lower=0, upper=50, prior=35, prior_confidence="low" ), some_cat=neps.Categorical( - choices=["a", "b", "c"], default="a", default_confidence="high" + choices=["a", "b", "c"], prior="a", prior_confidence="high" ), ) logging.basicConfig(level=logging.INFO) neps.run( - run_pipeline=run_pipeline, + evaluate_pipeline=evaluate_pipeline, pipeline_space=pipeline_space, root_directory="results/user_priors_with_graphs", max_evaluations_total=15, diff --git a/neps_examples/experimental/fault_tolerance.py b/neps_examples/experimental/fault_tolerance.py index 8406627e..8399d2ad 100644 --- a/neps_examples/experimental/fault_tolerance.py +++ b/neps_examples/experimental/fault_tolerance.py @@ -2,6 +2,7 @@ """ import logging +from warnings import warn import torch import torch.nn.functional as F @@ -40,6 +41,10 @@ def get_model_and_optimizer(learning_rate): def run_pipeline(pipeline_directory, learning_rate): + warn("run_pipeline is deprecated, use evaluate_pipeline instead", DeprecationWarning) + return evaluate_pipeline(pipeline_directory, learning_rate) + +def evaluate_pipeline(pipeline_directory, learning_rate): model, optimizer = get_model_and_optimizer(learning_rate) checkpoint_path = pipeline_directory / "checkpoint.pth" @@ -83,7 +88,7 @@ def run_pipeline(pipeline_directory, learning_rate): logging.basicConfig(level=logging.INFO) neps.run( - run_pipeline=run_pipeline, + evaluate_pipeline=evaluate_pipeline, pipeline_space=pipeline_space, root_directory="results/fault_tolerance_example", max_evaluations_total=15, diff --git a/neps_examples/template/basic_template.py b/neps_examples/template/basic_template.py index 87e6c3ae..779e723a 100644 --- a/neps_examples/template/basic_template.py +++ b/neps_examples/template/basic_template.py @@ -9,22 +9,23 @@ The 2 crucial components are: * The search space, called the `pipeline_space` in NePS * This defines the set of hyperparameters that the optimizer will search over - * This declaration also allows injecting priors in the form of defaults per hyperparameter -* The `run_pipeline` function + * This declaration also allows injecting priors per hyperparameter +* The `evaluate_pipeline` function * This function is called by the optimizer and is responsible for running the pipeline * The function should at the minimum expect the hyperparameters as keyword arguments - * The function should return the loss of the pipeline as a float - * If the return value is a dictionary, it should have a key called "loss" with the loss as a float + * The function should return the objective_to_minimize of the pipeline as a float + * If the return value is a dictionary, it should have a key called "objective_to_minimize" with the objective_to_minimize as a float Overall, running an optimizer from NePS involves 4 clear steps: 1. Importing neccessary packages including neps. 2. Designing the search space as a dictionary. -3. Creating the run_pipeline and returning the loss and other wanted metrics. +3. Creating the evaluate_pipeline and returning the objective_to_minimize and other wanted metrics. 4. Using neps run with the optimizer of choice. """ import logging +from warnings import warn import neps @@ -40,13 +41,16 @@ def pipeline_space() -> dict: lower=1e-5, upper=1e-2, log=True, # If True, the search space is sampled in log space - default=1e-3, # a non-None value here acts as the mode of the prior distribution + prior=1e-3, # a non-None value here acts as the mode of the prior distribution ), ) return space - def run_pipeline(**config) -> dict | float: + warn("run_pipeline is deprecated, use evaluate_pipeline instead", DeprecationWarning) + return evaluate_pipeline(**config) + +def evaluate_pipeline(**config) -> dict | float: # Run pipeline should include the following steps: # 1. Defining the model. @@ -56,7 +60,7 @@ def run_pipeline(**config) -> dict | float: # learning_rate = config["lr"] # 3. The training loop # 3.1 Save any checkpoint if necessary - # 4. Returning the loss, which can be either as a single float or as part of + # 4. Returning the objective_to_minimize, which can be either as a single float or as part of # an info dictionary containing other metrics. # Can use global logger to log any information @@ -74,7 +78,7 @@ def run_pipeline(**config) -> dict | float: # https://github.com/automl/neps/tree/master/neps/optimizers/README.md neps.run( - run_pipeline=run_pipeline, # User TODO (defined above) + evaluate_pipeline=evaluate_pipeline, # User TODO (defined above) pipeline_space=pipeline_space(), # User TODO (defined above) root_directory="results", max_evaluations_total=10, diff --git a/neps_examples/template/ifbo_template.py b/neps_examples/template/ifbo_template.py index 9e99c820..d069c471 100644 --- a/neps_examples/template/ifbo_template.py +++ b/neps_examples/template/ifbo_template.py @@ -1,26 +1,36 @@ +from warnings import warn + import numpy as np from neps.plot.plot3D import Plotter3D -from .priorband_template import pipeline_space, run_pipeline +from .priorband_template import pipeline_space, evaluate_pipeline ASSUMED_MAX_LOSS = 10 - def ifbo_run_pipeline( pipeline_directory, # The directory where the config is saved previous_pipeline_directory, # The directory of the config's immediate lower fidelity **config, # The hyperparameters to be used in the pipeline ) -> dict | float: - result_dict = run_pipeline( + # NOTE: Deprecation warning + warn("ifbo_run_pipeline is deprecated, use ifbo_evaluate_pipeline instead", DeprecationWarning) + return ifbo_evaluate_pipeline(pipeline_directory, previous_pipeline_directory, **config) + +def ifbo_evaluate_pipeline( + pipeline_directory, # The directory where the config is saved + previous_pipeline_directory, # The directory of the config's immediate lower fidelity + **config, # The hyperparameters to be used in the pipeline +) -> dict | float: + result_dict = evaluate_pipeline( pipeline_directory=pipeline_directory, # NOTE: can only support <=10 HPs and no categoricals previous_pipeline_directory=previous_pipeline_directory, **config, ) - # NOTE: Normalize the loss to be between 0 and 1 + # NOTE: Normalize the objective_to_minimize to be between 0 and 1 ## crucial for ifBO's FT-PFN surrogate to work as expected - result_dict["loss"] = np.clip(result_dict["loss"], 0, ASSUMED_MAX_LOSS) / ASSUMED_MAX_LOSS + result_dict["objective_to_minimize"] = np.clip(result_dict["objective_to_minimize"], 0, ASSUMED_MAX_LOSS) / ASSUMED_MAX_LOSS return result_dict @@ -28,10 +38,10 @@ def ifbo_run_pipeline( import neps neps.run( - run_pipeline=run_pipeline, + evaluate_pipeline=evaluate_pipeline, pipeline_space=pipeline_space(), root_directory="results", max_evaluations_total=50, searcher="ifbo", ) -# end of ifbo_run_pipeline \ No newline at end of file +# end of ifbo_evaluate_pipeline diff --git a/neps_examples/template/lightning_template.py b/neps_examples/template/lightning_template.py index d284b692..c1ecbad4 100644 --- a/neps_examples/template/lightning_template.py +++ b/neps_examples/template/lightning_template.py @@ -10,28 +10,29 @@ The 3 crucial components are: * The search space, called the `pipeline_space` in NePS * This defines the set of hyperparameters that the optimizer will search over - * This declaration also allows injecting priors in the form of defaults per hyperparameter + * This declaration also allows injecting priors per hyperparameter * The `lightning module` * This defines the training, validation, and testing of the model * This distributes the hyperparameters * This can be used to create the Dataloaders for training, validation, and testing -* The `run_pipeline` function +* The `evaluate_pipeline` function * This function is called by the optimizer and is responsible for running the pipeline * The function should at the minimum expect the hyperparameters as keyword arguments - * The function should return the loss of the pipeline as a float - * If the return value is a dictionary, it should have a key called "loss" with the loss as a float + * The function should return the objective_to_minimize of the pipeline as a float + * If the return value is a dictionary, it should have a key called "objective_to_minimize" with the objective_to_minimize as a float Overall, running an optimizer from NePS with Lightning involves 5 clear steps: 1. Importing neccessary packages including NePS and Lightning. 2. Designing the search space as a dictionary. 3. Creating the LightningModule with the required parameters -4. Creating the run_pipeline and returning the loss and other wanted metrics. +4. Creating the evaluate_pipeline and returning the objective_to_minimize and other wanted metrics. 5. Using neps run with the optimizer of choice. For a more detailed guide, please refer to: https://github.com/automl/neps/blob/master/neps_examples/convenience/neps_x_lightning.py """ import logging +from warnings import warn import lightning as L import torch @@ -52,9 +53,9 @@ def pipeline_space() -> dict: lower=1e-5, upper=1e-2, log=True, # If True, the search space is sampled in log space - default=1e-3, # a non-None value here acts as the mode of the prior distribution + prior=1e-3, # a non-None value here acts as the mode of the prior distribution ), - optimizer=neps.Categorical(choices=["Adam", "SGD"], default="Adam"), + optimizer=neps.Categorical(choices=["Adam", "SGD"], prior="Adam"), epochs=neps.Integer( lower=1, upper=9, @@ -108,7 +109,11 @@ def configure_optimizers(self) -> torch.optim.Optimizer: # https://github.com/automl/neps/blob/master/neps_examples/convenience/neps_x_lightning.py -def run_pipeline( +def run_pipeline(pipeline_directory, previous_pipeline_directory, **config) -> dict | float: + warn("run_pipeline is deprecated, use evaluate_pipeline instead", DeprecationWarning) + return evaluate_pipeline(pipeline_directory, previous_pipeline_directory, **config) + +def evaluate_pipeline( pipeline_directory, # The directory where the config is saved previous_pipeline_directory, # The directory of the config's immediate lower fidelity **config, # The hyperparameters to be used in the pipeline @@ -149,27 +154,27 @@ def run_pipeline( trainer.fit(model, ckpt_path=checkpoint_path) else: trainer.fit(model) - val_loss = trainer.logged_metrics.get("val_loss", None) + val_objective_to_minimize = trainer.logged_metrics.get("val_objective_to_minimize", None) trainer.test(model) - test_loss = trainer.logged_metrics.get("test_loss", None) + test_objective_to_minimize = trainer.logged_metrics.get("test_objective_to_minimize", None) - # Return a dictionary with the results, or a single float value (loss) + # Return a dictionary with the results, or a single float value (objective_to_minimize) return { - "loss": val_loss, + "objective_to_minimize": val_objective_to_minimize, "info_dict": { - "test_loss": test_loss, + "test_objective_to_minimize": test_objective_to_minimize, }, } -# end of run_pipeline +# end of evaluate_pipeline if __name__ == "__main__": neps.run( - run_pipeline=run_pipeline, # User TODO (defined above) + evaluate_pipeline=evaluate_pipeline, # User TODO (defined above) pipeline_space=pipeline_space(), # User TODO (defined above) root_directory="results", - max_evaluations_total=25, # total number of times `run_pipeline` is called + max_evaluations_total=25, # total number of times `evaluate_pipeline` is called searcher="priorband", # "priorband_bo" for longer budgets, and set `initial_design_size`` ) diff --git a/neps_examples/template/priorband_template.py b/neps_examples/template/priorband_template.py index e2d75433..fb68027c 100644 --- a/neps_examples/template/priorband_template.py +++ b/neps_examples/template/priorband_template.py @@ -11,21 +11,22 @@ * The search space, called the `pipeline_space` in NePS * This defines the set of hyperparameters that the optimizer will search over * This declaration also allows injecting priors in the form of defaults per hyperparameter -* The `run_pipeline` function +* The `evaluate_pipeline` function * This function is called by the optimizer and is responsible for running the pipeline * The function should at the minimum expect the hyperparameters as keyword arguments - * The function should return the loss of the pipeline as a float - * If the return value is a dictionary, it should have a key called "loss" with the loss as a float + * The function should return the objective_to_minimize of the pipeline as a float + * If the return value is a dictionary, it should have a key called "objective_to_minimize" with the objective_to_minimize as a float Overall, running an optimizer from NePS involves 4 clear steps: 1. Importing neccessary packages including neps. 2. Designing the search space as a dictionary. -3. Creating the run_pipeline and returning the loss and other wanted metrics. +3. Creating the evaluate_pipeline and returning the objective_to_minimize and other wanted metrics. 4. Using neps run with the optimizer of choice. """ import logging +from warnings import warn import torch import torch.nn as nn @@ -45,13 +46,13 @@ def pipeline_space() -> dict: lower=1e-5, upper=1e-2, log=True, # If True, the search space is sampled in log space - default=1e-3, # a non-None value here acts as the mode of the prior distribution + prior=1e-3, # a non-None value here acts as the mode of the prior distribution ), wd=neps.Float( lower=0, upper=1e-1, log=True, - default=1e-3, + prior=1e-3, ), epoch=neps.Integer( lower=1, @@ -61,8 +62,12 @@ def pipeline_space() -> dict: ) return space +def run_pipeline(**config) -> dict | float: + warn("run_pipeline is deprecated, use evaluate_pipeline instead", DeprecationWarning) + return evaluate_pipeline(**config) -def run_pipeline( + +def evaluate_pipeline( pipeline_directory, # The directory where the config is saved previous_pipeline_directory, # The directory of the config's immediate lower fidelity **config, # The hyperparameters to be used in the pipeline @@ -129,9 +134,9 @@ def forward(self, x): optimizer=optimizer, ) - # Return a dictionary with the results, or a single float value (loss) + # Return a dictionary with the results, or a single float value (objective_to_minimize) return { - "loss": ..., + "objective_to_minimize": ..., "info_dict": { "train_accuracy": ..., "test_accuracy": ..., @@ -139,14 +144,14 @@ def forward(self, x): } -# end of run_pipeline +# end of evaluate_pipeline if __name__ == "__main__": neps.run( - run_pipeline=run_pipeline, # User TODO (defined above) + evaluate_pipeline=evaluate_pipeline, # User TODO (defined above) pipeline_space=pipeline_space(), # User TODO (defined above) root_directory="results", - max_evaluations_total=25, # total number of times `run_pipeline` is called + max_evaluations_total=25, # total number of times `evaluate_pipeline` is called searcher="priorband", # "priorband_bo" for longer budgets, and set `initial_design_size`` ) diff --git a/tests/regression_objectives.py b/tests/regression_objectives.py index e6bec6c5..eb261674 100644 --- a/tests/regression_objectives.py +++ b/tests/regression_objectives.py @@ -39,6 +39,7 @@ def pipeline_space(self, value): @property def run_pipeline(self) -> Callable: + warnings.warn("run_pipeline is deprecated, use evaluate_pipeline instead", DeprecationWarning) if self._run_pipeline is None: raise NotImplementedError( f"run_pipeline can not be None, " @@ -48,8 +49,24 @@ def run_pipeline(self) -> Callable: else: return self._run_pipeline + @property + def evaluate_pipeline(self) -> Callable: + if self._run_pipeline is None: + raise NotImplementedError( + f"evaluate_pipeline can not be None, " + f"the subclass {type(self)} must " + f"implement a evaluate_pipeline Callable" + ) + else: + return self._run_pipeline + @run_pipeline.setter def run_pipeline(self, value): + warnings.warn("run_pipeline is deprecated, use evaluate_pipeline instead", DeprecationWarning) + self._run_pipeline = value + + @evaluate_pipeline.setter + def evaluate_pipeline(self, value): self._run_pipeline = value def __call__(self, *args, **kwargs) -> dict[str, Any]: @@ -73,11 +90,11 @@ def cost_evaluation(**joint_configuration): results = self.benchmark(joint_configuration, nepochs=epoch) return { - "loss": 100 - results[epoch]["valid-acc"], + "objective_to_minimize": 100 - results[epoch]["valid-acc"], "cost": results[epoch]["runtime"], } - def loss_evaluation(**joint_configuration): + def objective_to_minimize_evaluation(**joint_configuration): epoch = joint_configuration.pop("epoch") joint_configuration.update({"N": 5, "W": 16, "Resolution": 1.0}) @@ -87,7 +104,7 @@ def loss_evaluation(**joint_configuration): if "cost" in self.optimizer: return cost_evaluation else: - return loss_evaluation + return objective_to_minimize_evaluation def __init__( self, @@ -171,10 +188,10 @@ def hartmann3(**z_nX): noise = np.abs(rng.normal(size=H.size)) * self.noise * (1 - log_z_scaled) - loss = float((H + noise)[0]) + objective_to_minimize = float((H + noise)[0]) cost = 0.05 + (1 - 0.05) * (z / self.z_max) ** 2 - result = {"loss": loss} + result = {"objective_to_minimize": objective_to_minimize} if "cost" in self.optimizer: result.update({"cost": cost}) @@ -230,10 +247,10 @@ def hartmann6(**z_nX): noise = np.abs(rng.normal(size=H.size)) * self.noise * (1 - log_z_scaled) - loss = float((H + noise)[0]) + objective_to_minimize = float((H + noise)[0]) cost = 0.05 + (1 - 0.05) * (z / self.z_max) ** 2 - result = {"loss": loss} + result = {"objective_to_minimize": objective_to_minimize} if "cost" in self.optimizer: result.update({"cost": cost}) diff --git a/tests/regression_runner.py b/tests/regression_runner.py index d102a1df..25d6a11c 100644 --- a/tests/regression_runner.py +++ b/tests/regression_runner.py @@ -51,7 +51,7 @@ def __init__( objective: RegressionObjectiveBase | Callable, iterations: int = 100, max_evaluations: int = 150, - budget: int = 10000, + max_cost_total: int = 10000, experiment_name: str = "", **kwargs, ): @@ -62,7 +62,7 @@ def __init__( objective: callable that takes a configuration as input and evaluates it iterations: number of times to record the whole optimization process max_evaluations: maximum number of total evaluations for each optimization process - budget: budget for cost aware optimizers + max_cost_total: budget for cost aware optimizers experiment_name: string to identify different experiments """ self.objective = objective @@ -98,7 +98,7 @@ def __init__( self.benchmark = None # Cost cooling optimizer expects budget but none of the others does - self.budget = budget if "cost" in self.optimizer else None + self.max_cost_total = max_cost_total if "cost" in self.optimizer else None self.max_evaluations = max_evaluations self.final_losses: list[float] = [] @@ -112,17 +112,17 @@ def root_directory(self): @property def final_losses_path(self): - return Path(self.root_directory, self.loss_file_name) + return Path(self.root_directory, self.objective_to_minimize_file_name) @property - def loss_file_name(self): + def objective_to_minimize_file_name(self): return f"final_losses_{self.max_evaluations}_.txt" def save_losses(self): if not self.final_losses_path.parent.exists(): Path(self.root_directory).mkdir() with self.final_losses_path.open(mode="w+", encoding="utf-8") as f: - f.writelines([str(loss) + "\n" for loss in self.final_losses]) + f.writelines([str(objective_to_minimize) + "\n" for objective_to_minimize in self.final_losses]) logging.info( f"Saved the results of {len(self.final_losses)} " f"runs of {self.max_evaluations} " @@ -131,10 +131,10 @@ def save_losses(self): def neps_run(self, working_directory: Path): neps.run( - run_pipeline=self.objective, + evaluate_pipeline=self.objective, pipeline_space=self.pipeline_space, searcher=self.optimizer, - max_cost_total=self.budget, + max_cost_total=self.max_cost_total, root_directory=working_directory, max_evaluations_total=self.max_evaluations, ) @@ -171,8 +171,8 @@ def read_results(self): elif self.final_losses_path.exists(): # Read from final_losses_path for each regression run self.final_losses = [ - float(loss) - for loss in self.final_losses_path.read_text( + float(objective_to_minimize) + for objective_to_minimize in self.final_losses_path.read_text( encoding="utf-8" ).splitlines()[: self.iterations] ] @@ -189,8 +189,8 @@ def read_results(self): # Try reading from the LOSS_FILE in the worst case if LOSS_FILE.exists(): with LOSS_FILE.open(mode="r", encoding="utf-8") as f: - loss_dict = json.load(f) - self.final_losses = loss_dict[self.optimizer][self.task] + objective_to_minimize_dict = json.load(f) + self.final_losses = objective_to_minimize_dict[self.optimizer][self.task] else: raise FileNotFoundError( f"Results from the previous runs are not " diff --git a/tests/test_neps_api/testing_scripts/baseoptimizer_neps.py b/tests/test_neps_api/testing_scripts/baseoptimizer_neps.py index 63ca6670..75038a40 100644 --- a/tests/test_neps_api/testing_scripts/baseoptimizer_neps.py +++ b/tests/test_neps_api/testing_scripts/baseoptimizer_neps.py @@ -1,4 +1,5 @@ import logging +from warnings import warn import neps from neps.optimizers.bayesian_optimization.optimizer import BayesianOptimization @@ -17,13 +18,21 @@ def run_pipeline(val1, val2): - loss = val1 * val2 - return loss + warn("run_pipeline is deprecated, use evaluate_pipeline instead", DeprecationWarning) + return evaluate_pipeline(val1, val2) + +def evaluate_pipeline(val1, val2): + objective_to_minimize = val1 * val2 + return objective_to_minimize def run_pipeline_fidelity(val1, val2): - loss = val1 * val2 - return {"loss": loss, "cost": 1} + warn("run_pipeline_fidelity is deprecated, use evaluate_pipeline_fidelity instead", DeprecationWarning) + return evaluate_pipeline_fidelity(val1, val2) + +def evaluate_pipeline_fidelity(val1, val2): + objective_to_minimize = val1 * val2 + return {"objective_to_minimize": objective_to_minimize, "cost": 1} logging.basicConfig(level=logging.INFO) @@ -34,7 +43,7 @@ def run_pipeline_fidelity(val1, val2): pipeline_space=search_space, initial_design_size=5 ) neps.run( - run_pipeline=run_pipeline, + evaluate_pipeline=evaluate_pipeline, root_directory="bo_custom_created", max_evaluations_total=1, searcher=my_custom_searcher_1, @@ -42,9 +51,9 @@ def run_pipeline_fidelity(val1, val2): # Case 2: Testing BaseOptimizer as searcher with Hyperband search_space_fidelity = SearchSpace(**pipeline_space_fidelity) -my_custom_searcher_2 = Hyperband(pipeline_space=search_space_fidelity, budget=1) +my_custom_searcher_2 = Hyperband(pipeline_space=search_space_fidelity, max_cost_total=1) neps.run( - run_pipeline=run_pipeline_fidelity, + evaluate_pipeline=evaluate_pipeline_fidelity, root_directory="hyperband_custom_created", max_cost_total=1, searcher=my_custom_searcher_2, diff --git a/tests/test_neps_api/testing_scripts/default_neps.py b/tests/test_neps_api/testing_scripts/default_neps.py index 11b41dca..c0c0905b 100644 --- a/tests/test_neps_api/testing_scripts/default_neps.py +++ b/tests/test_neps_api/testing_scripts/default_neps.py @@ -1,15 +1,16 @@ import logging +from warnings import warn import neps pipeline_space_fidelity_priors = dict( - val1=neps.Float(lower=-10, upper=10, default=1), + val1=neps.Float(lower=-10, upper=10, prior=1), val2=neps.Integer(lower=1, upper=5, is_fidelity=True), ) pipeline_space_not_fidelity_priors = dict( - val1=neps.Float(lower=-10, upper=10, default=1), - val2=neps.Integer(lower=1, upper=5, default=1), + val1=neps.Float(lower=-10, upper=10, prior=1), + val2=neps.Integer(lower=1, upper=5, prior=1), ) pipeline_space_fidelity = dict( @@ -24,8 +25,12 @@ def run_pipeline(val1, val2): - loss = val1 * val2 - return loss + warn("run_pipeline is deprecated, use evaluate_pipeline instead", DeprecationWarning) + return evaluate_pipeline(val1, val2) + +def evaluate_pipeline(val1, val2): + objective_to_minimize = val1 * val2 + return objective_to_minimize logging.basicConfig(level=logging.INFO) @@ -36,7 +41,7 @@ def run_pipeline(val1, val2): # Case 1: Choosing priorband neps.run( - run_pipeline=run_pipeline, + evaluate_pipeline=evaluate_pipeline, pipeline_space=pipeline_space_fidelity_priors, root_directory="priorband_bo_user_decided", max_evaluations_total=1, @@ -50,7 +55,7 @@ def run_pipeline(val1, val2): # Case 1: Choosing priorband neps.run( - run_pipeline=run_pipeline, + evaluate_pipeline=evaluate_pipeline, pipeline_space=pipeline_space_fidelity_priors, root_directory="priorband_neps_decided", max_evaluations_total=1, @@ -60,7 +65,7 @@ def run_pipeline(val1, val2): # Case 2: Choosing bayesian_optimization neps.run( - run_pipeline=run_pipeline, + evaluate_pipeline=evaluate_pipeline, pipeline_space=pipeline_space_not_fidelity, root_directory="bo_neps_decided", max_evaluations_total=1, @@ -68,7 +73,7 @@ def run_pipeline(val1, val2): # Case 3: Choosing pibo neps.run( - run_pipeline=run_pipeline, + evaluate_pipeline=evaluate_pipeline, pipeline_space=pipeline_space_not_fidelity_priors, root_directory="pibo_neps_decided", max_evaluations_total=1, @@ -77,7 +82,7 @@ def run_pipeline(val1, val2): # Case 4: Choosing hyperband neps.run( - run_pipeline=run_pipeline, + evaluate_pipeline=evaluate_pipeline, pipeline_space=pipeline_space_fidelity, root_directory="hyperband_neps_decided", max_evaluations_total=1, 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 d28cbbed..e32d84d4 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,7 @@ import logging import os from pathlib import Path +from warnings import warn import neps pipeline_space = dict( @@ -10,8 +11,9 @@ def run_pipeline(val1, val2): - loss = val1 * val2 - return loss + warn("run_pipeline is deprecated, use evaluate_pipeline instead", DeprecationWarning) + objective_to_minimize = val1 * val2 + return objective_to_minimize logging.basicConfig(level=logging.INFO) @@ -21,7 +23,7 @@ def run_pipeline(val1, val2): parent_directory = os.path.join(script_directory, os.pardir) searcher_path = Path(parent_directory) / "testing_yaml" / "optimizer_test" neps.run( - run_pipeline=run_pipeline, + evaluate_pipeline=run_pipeline, pipeline_space=pipeline_space, root_directory="user_yaml_bo", max_evaluations_total=1, diff --git a/tests/test_runtime/test_default_report_values.py b/tests/test_runtime/test_default_report_values.py index 265d4c08..1a87aecf 100644 --- a/tests/test_runtime/test_default_report_values.py +++ b/tests/test_runtime/test_default_report_values.py @@ -17,7 +17,7 @@ def neps_state(tmp_path: Path) -> NePSState[Path]: return create_or_load_filebased_neps_state( directory=tmp_path / "neps_state", optimizer_info=OptimizerInfo(info={"nothing": "here"}), - optimizer_state=OptimizationState(budget=None, shared_state={}), + optimizer_state=OptimizationState(max_cost_total_info=None, shared_state={}), ) @@ -28,7 +28,7 @@ def test_default_values_on_error( settings = WorkerSettings( on_error=OnErrorPossibilities.IGNORE, default_report_values=DefaultReportValues( - loss_value_on_error=2.4, # <- Highlight + objective_to_minimize_value_on_error=2.4, # <- Highlight cost_value_on_error=2.4, # <- Highlight learning_curve_on_error=[2.4, 2.5], # <- Highlight ), @@ -67,7 +67,7 @@ def eval_function(*args, **kwargs) -> float: trial = trials.popitem()[1] assert trial.state == Trial.State.CRASHED assert trial.report is not None - assert trial.report.loss == 2.4 + assert trial.report.objective_to_minimize == 2.4 assert trial.report.cost == 2.4 assert trial.report.learning_curve == [2.4, 2.5] @@ -121,13 +121,13 @@ def eval_function(*args, **kwargs) -> float: assert trial.report.learning_curve == [2.4, 2.5] -def test_default_value_loss_curve_take_loss_value( +def test_default_value_objective_to_minimize_curve_take_objective_to_minimize_value( neps_state: NePSState, ) -> None: optimizer = RandomSearch(pipeline_space=SearchSpace(a=Float(0, 1))) settings = WorkerSettings( on_error=OnErrorPossibilities.IGNORE, - default_report_values=DefaultReportValues(learning_curve_if_not_provided="loss"), + default_report_values=DefaultReportValues(learning_curve_if_not_provided="objective_to_minimize"), max_evaluations_total=None, include_in_progress_evaluations_towards_maximum=False, max_cost_total=None, diff --git a/tests/test_runtime/test_error_handling_strategies.py b/tests/test_runtime/test_error_handling_strategies.py index 05cf762a..ca401060 100644 --- a/tests/test_runtime/test_error_handling_strategies.py +++ b/tests/test_runtime/test_error_handling_strategies.py @@ -22,7 +22,7 @@ def neps_state(tmp_path: Path) -> NePSState[Path]: return create_or_load_filebased_neps_state( directory=tmp_path / "neps_state", optimizer_info=OptimizerInfo(info={"nothing": "here"}), - optimizer_state=OptimizationState(budget=None, shared_state={}), + optimizer_state=OptimizationState(max_cost_total_info=None, shared_state={}), ) diff --git a/tests/test_runtime/test_stopping_criterion.py b/tests/test_runtime/test_stopping_criterion.py index c73051a9..3410042f 100644 --- a/tests/test_runtime/test_stopping_criterion.py +++ b/tests/test_runtime/test_stopping_criterion.py @@ -18,7 +18,7 @@ def neps_state(tmp_path: Path) -> NePSState[Path]: return create_or_load_filebased_neps_state( directory=tmp_path / "neps_state", optimizer_info=OptimizerInfo(info={"nothing": "here"}), - optimizer_state=OptimizationState(budget=None, shared_state={}), + optimizer_state=OptimizationState(max_cost_total_info=None, shared_state={}), ) @@ -59,7 +59,7 @@ def eval_function(*args, **kwargs) -> float: for _, trial in trials.items(): assert trial.state == Trial.State.SUCCESS assert trial.report is not None - assert trial.report.loss == 1.0 + assert trial.report.objective_to_minimize == 1.0 # New worker has the same total number of evaluations so it should not run anything. new_worker = DefaultWorker.new( @@ -113,7 +113,7 @@ def eval_function(*args, **kwargs) -> float: for _, trial in trials.items(): assert trial.state == Trial.State.SUCCESS assert trial.report is not None - assert trial.report.loss == 1.0 + assert trial.report.objective_to_minimize == 1.0 # New worker should run 2 more evaluations new_worker = DefaultWorker.new( @@ -134,7 +134,7 @@ def eval_function(*args, **kwargs) -> float: for _, trial in trials.items(): assert trial.state == Trial.State.SUCCESS assert trial.report is not None - assert trial.report.loss == 1.0 + assert trial.report.objective_to_minimize == 1.0 def test_include_in_progress_evaluations_towards_maximum_with_work_eval_count( @@ -190,7 +190,7 @@ def eval_function(*args, **kwargs) -> float: assert the_completed_trial.state == Trial.State.SUCCESS assert the_completed_trial.report is not None - assert the_completed_trial.report.loss == 1.0 + assert the_completed_trial.report.objective_to_minimize == 1.0 def test_max_cost_total( @@ -211,7 +211,7 @@ def test_max_cost_total( ) def eval_function(*args, **kwargs) -> dict: - return {"loss": 1.0, "cost": 1.0} + return {"objective_to_minimize": 1.0, "cost": 1.0} worker = DefaultWorker.new( state=neps_state, @@ -262,7 +262,7 @@ def test_worker_cost_total( ) def eval_function(*args, **kwargs) -> dict: - return {"loss": 1.0, "cost": 1.0} + return {"objective_to_minimize": 1.0, "cost": 1.0} worker = DefaultWorker.new( state=neps_state, diff --git a/tests/test_settings/test_settings.py b/tests/test_settings/test_settings.py index 1244bcf6..e943e721 100644 --- a/tests/test_settings/test_settings.py +++ b/tests/test_settings/test_settings.py @@ -4,7 +4,7 @@ import pytest from pathlib import Path from tests.test_yaml_run_args.test_yaml_run_args import ( - run_pipeline, + evaluate_pipeline, hook1, hook2, pipeline_space, @@ -12,7 +12,7 @@ from neps.optimizers.bayesian_optimization.optimizer import BayesianOptimization BASE_PATH = Path("tests") / "test_settings" -run_pipeline = run_pipeline +evaluate_pipeline = evaluate_pipeline hook1 = hook1 hook2 = hook2 pipeline_space = pipeline_space @@ -25,7 +25,7 @@ [ ( { # only essential arguments provided by func_args, no yaml - "run_pipeline": run_pipeline, + "evaluate_pipeline": evaluate_pipeline, "root_directory": "path/to/root_directory", "pipeline_space": pipeline_space, "run_args": Default(None), @@ -38,7 +38,7 @@ "continue_until_max_evaluation_completed": Default(False), "max_cost_total": Default(None), "ignore_errors": Default(False), - "loss_value_on_error": Default(None), + "objective_to_minimize_value_on_error": Default(None), "cost_value_on_error": Default(None), "pre_load_hooks": Default(None), "searcher": Default("default"), @@ -46,7 +46,7 @@ }, Default(None), { - "run_pipeline": run_pipeline, + "evaluate_pipeline": evaluate_pipeline, "root_directory": "path/to/root_directory", "pipeline_space": pipeline_space, "overwrite_working_directory": False, @@ -58,7 +58,7 @@ "continue_until_max_evaluation_completed": False, "max_cost_total": None, "ignore_errors": False, - "loss_value_on_error": None, + "objective_to_minimize_value_on_error": None, "cost_value_on_error": None, "pre_load_hooks": None, "searcher": "default", @@ -67,7 +67,7 @@ ), ( { # only required elements of run_args - "run_pipeline": Default(None), + "evaluate_pipeline": Default(None), "root_directory": Default(None), "pipeline_space": Default(None), "run_args": Default(None), @@ -80,7 +80,7 @@ "continue_until_max_evaluation_completed": Default(False), "max_cost_total": Default(None), "ignore_errors": Default(False), - "loss_value_on_error": Default(None), + "objective_to_minimize_value_on_error": Default(None), "cost_value_on_error": Default(None), "pre_load_hooks": Default(None), "searcher": Default("default"), @@ -88,7 +88,7 @@ }, "run_args_required.yaml", { - "run_pipeline": run_pipeline, + "evaluate_pipeline": evaluate_pipeline, "root_directory": "path/to/root_directory", "pipeline_space": pipeline_space, "overwrite_working_directory": False, @@ -100,7 +100,7 @@ "continue_until_max_evaluation_completed": False, "max_cost_total": None, "ignore_errors": False, - "loss_value_on_error": None, + "objective_to_minimize_value_on_error": None, "cost_value_on_error": None, "pre_load_hooks": None, "searcher": "default", @@ -109,7 +109,7 @@ ), ( { # required via func_args, optional via yaml - "run_pipeline": run_pipeline, + "evaluate_pipeline": evaluate_pipeline, "root_directory": "path/to/root_directory", "pipeline_space": pipeline_space, "run_args": "tests/path/to/run_args", # will be ignored by Settings @@ -122,7 +122,7 @@ "continue_until_max_evaluation_completed": Default(False), "max_cost_total": Default(None), "ignore_errors": Default(False), - "loss_value_on_error": Default(None), + "objective_to_minimize_value_on_error": Default(None), "cost_value_on_error": Default(None), "pre_load_hooks": Default(None), "searcher": Default("default"), @@ -130,7 +130,7 @@ }, "run_args_optional.yaml", { - "run_pipeline": run_pipeline, + "evaluate_pipeline": evaluate_pipeline, "root_directory": "path/to/root_directory", "pipeline_space": pipeline_space, "overwrite_working_directory": True, @@ -142,7 +142,7 @@ "continue_until_max_evaluation_completed": False, "max_cost_total": None, "ignore_errors": False, - "loss_value_on_error": None, + "objective_to_minimize_value_on_error": None, "cost_value_on_error": None, "pre_load_hooks": None, "searcher": "hyperband", @@ -151,7 +151,7 @@ ), ( { # overwrite all yaml values - "run_pipeline": run_pipeline, + "evaluate_pipeline": evaluate_pipeline, "root_directory": "path/to/root_directory", "pipeline_space": pipeline_space, "run_args": "test", @@ -164,7 +164,7 @@ "continue_until_max_evaluation_completed": False, "max_cost_total": None, "ignore_errors": False, - "loss_value_on_error": None, + "objective_to_minimize_value_on_error": None, "cost_value_on_error": None, "pre_load_hooks": None, "searcher": "default", @@ -172,7 +172,7 @@ }, "overwrite_run_args.yaml", { - "run_pipeline": run_pipeline, + "evaluate_pipeline": evaluate_pipeline, "root_directory": "path/to/root_directory", "pipeline_space": pipeline_space, "overwrite_working_directory": False, @@ -184,7 +184,7 @@ "continue_until_max_evaluation_completed": False, "max_cost_total": None, "ignore_errors": False, - "loss_value_on_error": None, + "objective_to_minimize_value_on_error": None, "cost_value_on_error": None, "pre_load_hooks": None, "searcher": "default", @@ -193,7 +193,7 @@ ), ( { # optimizer args special case - "run_pipeline": run_pipeline, + "evaluate_pipeline": evaluate_pipeline, "root_directory": "path/to/root_directory", "pipeline_space": pipeline_space, "run_args": "test", @@ -206,7 +206,7 @@ "continue_until_max_evaluation_completed": False, "max_cost_total": None, "ignore_errors": False, - "loss_value_on_error": None, + "objective_to_minimize_value_on_error": None, "cost_value_on_error": None, "pre_load_hooks": None, "searcher": Default("default"), @@ -220,7 +220,7 @@ }, "run_args_optimizer_settings.yaml", { - "run_pipeline": run_pipeline, + "evaluate_pipeline": evaluate_pipeline, "root_directory": "path/to/root_directory", "pipeline_space": pipeline_space, "overwrite_working_directory": False, @@ -232,7 +232,7 @@ "continue_until_max_evaluation_completed": False, "max_cost_total": None, "ignore_errors": False, - "loss_value_on_error": None, + "objective_to_minimize_value_on_error": None, "cost_value_on_error": None, "pre_load_hooks": None, "searcher": { @@ -255,7 +255,7 @@ ), ( { # load optimizer with args - "run_pipeline": Default(None), + "evaluate_pipeline": Default(None), "root_directory": Default(None), "pipeline_space": Default(None), "run_args": Default(None), @@ -268,7 +268,7 @@ "continue_until_max_evaluation_completed": Default(False), "max_cost_total": Default(None), "ignore_errors": Default(False), - "loss_value_on_error": Default(None), + "objective_to_minimize_value_on_error": Default(None), "cost_value_on_error": Default(None), "pre_load_hooks": Default(None), "searcher": Default("default"), @@ -278,7 +278,7 @@ }, "run_args_optimizer_outside.yaml", { - "run_pipeline": run_pipeline, + "evaluate_pipeline": evaluate_pipeline, "root_directory": "path/to/root_directory", "pipeline_space": pipeline_space, "overwrite_working_directory": True, @@ -290,7 +290,7 @@ "continue_until_max_evaluation_completed": False, "max_cost_total": None, "ignore_errors": False, - "loss_value_on_error": None, + "objective_to_minimize_value_on_error": None, "cost_value_on_error": None, "pre_load_hooks": None, "searcher": my_bayesian, @@ -331,7 +331,7 @@ def test_check_settings(func_args: dict, yaml_args: str, expected_output: dict) "continue_until_max_evaluation_completed": Default(False), "max_cost_total": Default(None), "ignore_errors": Default(False), - "loss_value_on_error": Default(None), + "objective_to_minimize_value_on_error": Default(None), "cost_value_on_error": Default(None), "pre_load_hooks": Default(None), "searcher": Default("default"), diff --git a/tests/test_state/test_filebased_neps_state.py b/tests/test_state/test_filebased_neps_state.py index 8b2d4eb5..ae29ef05 100644 --- a/tests/test_state/test_filebased_neps_state.py +++ b/tests/test_state/test_filebased_neps_state.py @@ -17,13 +17,13 @@ @fixture -@parametrize("budget", [BudgetInfo(max_cost_budget=10, used_cost_budget=0), None]) +@parametrize("max_cost_total_info", [BudgetInfo(max_cost_total=10, used_cost_budget=0), None]) @parametrize("shared_state", [{"a": "b"}, {}]) def optimizer_state( - budget: BudgetInfo | None, + max_cost_total_info: BudgetInfo | None, shared_state: dict[str, Any], ) -> OptimizationState: - return OptimizationState(budget=budget, shared_state=shared_state) + return OptimizationState(max_cost_total_info=max_cost_total_info, shared_state=shared_state) @fixture @@ -71,7 +71,7 @@ def test_create_or_load_with_load_filebased_neps_state( # that we prioritize what's in the existing data over what # was passed in. different_state = OptimizationState( - budget=BudgetInfo(max_cost_budget=20, used_cost_budget=10), + max_cost_total_info=BudgetInfo(max_cost_total=20, used_cost_budget=10), shared_state={"c": "d"}, ) neps_state2 = create_or_load_filebased_neps_state( diff --git a/tests/test_state/test_neps_state.py b/tests/test_state/test_neps_state.py index 51773fdb..e298223b 100644 --- a/tests/test_state/test_neps_state.py +++ b/tests/test_state/test_neps_state.py @@ -49,20 +49,20 @@ def case_search_space_with_fid() -> SearchSpace: @case def case_search_space_no_fid_with_prior() -> SearchSpace: return SearchSpace( - a=Float(0, 1, default=0.5), - b=Categorical(["a", "b", "c"], default="a"), + a=Float(0, 1, prior=0.5), + b=Categorical(["a", "b", "c"], prior="a"), c=Constant("a"), - d=Integer(0, 10, default=5), + d=Integer(0, 10, prior=5), ) @case def case_search_space_fid_with_prior() -> SearchSpace: return SearchSpace( - a=Float(0, 1, default=0.5), - b=Categorical(["a", "b", "c"], default="a"), + a=Float(0, 1, prior=0.5), + b=Categorical(["a", "b", "c"], prior="a"), c=Constant("a"), - d=Integer(0, 10, default=5), + d=Integer(0, 10, prior=5), e=Integer(1, 10, is_fidelity=True), ) @@ -126,7 +126,7 @@ def optimizer_and_key(key: str, search_space: SearchSpace) -> tuple[BaseOptimize if key in JUST_SKIP: pytest.xfail(f"{key} is not instantiable") - if key in REQUIRES_PRIOR and search_space.hyperparameters["a"].default is None: + if key in REQUIRES_PRIOR and search_space.hyperparameters["a"].prior is None: pytest.xfail(f"{key} requires a prior") if len(search_space.fidelities) > 0 and key in OPTIMIZER_FAILS_WITH_FIDELITY: @@ -139,7 +139,7 @@ def optimizer_and_key(key: str, search_space: SearchSpace) -> tuple[BaseOptimize "pipeline_space": search_space, } if key in OPTIMIZER_REQUIRES_BUDGET: - kwargs["budget"] = 10 + kwargs["max_cost_total"] = 10 optimizer_cls = SearcherMapping[key] @@ -147,11 +147,11 @@ def optimizer_and_key(key: str, search_space: SearchSpace) -> tuple[BaseOptimize @parametrize("optimizer_info", [OptimizerInfo({"a": "b"}), OptimizerInfo({})]) -@parametrize("budget", [BudgetInfo(max_cost_budget=10, used_cost_budget=0), None]) +@parametrize("max_cost_total", [BudgetInfo(max_cost_total=10, used_cost_budget=0), None]) @parametrize("shared_state", [{"a": "b"}, {}]) def case_neps_state_filebased( tmp_path: Path, - budget: BudgetInfo | None, + max_cost_total: BudgetInfo | None, optimizer_info: OptimizerInfo, shared_state: dict[str, Any], ) -> NePSState: @@ -159,7 +159,7 @@ def case_neps_state_filebased( return create_or_load_filebased_neps_state( directory=new_path, optimizer_info=optimizer_info, - optimizer_state=OptimizationState(budget=budget, shared_state=shared_state), + optimizer_state=OptimizationState(max_cost_total_info=max_cost_total, shared_state=shared_state), ) @@ -169,7 +169,7 @@ def test_sample_trial( optimizer_and_key: tuple[BaseOptimizer, str], ) -> None: optimizer, key = optimizer_and_key - if key in REQUIRES_COST and neps_state.optimizer_state().budget is None: + if key in REQUIRES_COST and neps_state.optimizer_state().max_cost_total_info is None: pytest.xfail(f"{key} requires a cost budget") assert neps_state.get_all_trials() == {} diff --git a/tests/test_state/test_synced.py b/tests/test_state/test_synced.py index 6294db37..eeeb19bd 100644 --- a/tests/test_state/test_synced.py +++ b/tests/test_state/test_synced.py @@ -91,7 +91,7 @@ def case_trial_3(tmp_path: Path) -> tuple[Synced[Trial, Path], Callable[[Trial], def _update(trial: Trial) -> None: trial.set_complete( time_end=3, - loss=1, + objective_to_minimize=1, cost=1, extra={"hi": [1, 2, 3]}, learning_curve=[1], @@ -129,7 +129,7 @@ def case_trial_4(tmp_path: Path) -> tuple[Synced[Trial, Path], Callable[[Trial], def _update(trial: Trial) -> None: trial.set_complete( time_end=3, - loss=np.nan, + objective_to_minimize=np.nan, cost=np.inf, extra={"hi": [1, 2, 3]}, report_as="failed", @@ -167,7 +167,7 @@ def case_trial_5(tmp_path: Path) -> tuple[Synced[Trial, Path], Callable[[Trial], def _update(trial: Trial) -> None: trial.set_complete( time_end=3, - loss=np.nan, + objective_to_minimize=np.nan, cost=np.inf, extra={"hi": [1, 2, 3]}, learning_curve=None, @@ -231,7 +231,7 @@ def case_trial_7(tmp_path: Path) -> tuple[Synced[Trial, Path], Callable[[Trial], trial.set_evaluating(time_started=2, worker_id=1) trial.set_complete( time_end=3, - loss=np.nan, + objective_to_minimize=np.nan, cost=np.inf, extra={"hi": [1, 2, 3]}, learning_curve=[1, 2, 3], @@ -339,19 +339,19 @@ def _update(optimizer_info: OptimizerInfo) -> None: @case @pytest.mark.parametrize( - "budget", (None, BudgetInfo(max_cost_budget=10, used_cost_budget=0)) + "max_cost_total_info", (None, BudgetInfo(max_cost_total=10, used_cost_budget=0)) ) @pytest.mark.parametrize("shared_state", ({}, {"a": "b"})) def case_optimization_state( tmp_path: Path, - budget: BudgetInfo | None, + max_cost_total_info: BudgetInfo | None, shared_state: dict[str, Any], ) -> tuple[Synced[OptimizationState, Path], Callable[[OptimizationState], None]]: - optimization_state = OptimizationState(budget=budget, shared_state=shared_state) + optimization_state = OptimizationState(max_cost_total_info=max_cost_total_info, shared_state=shared_state) def _update(optimization_state: OptimizationState) -> None: optimization_state.shared_state["a"] = "c" # type: ignore # NOTE: We shouldn't be mutating but anywho... - optimization_state.budget = BudgetInfo(max_cost_budget=10, used_cost_budget=5) + optimization_state.max_cost_total_info = BudgetInfo(max_cost_total=10, used_cost_budget=5) x = Synced.new( data=optimization_state, diff --git a/tests/test_state/test_trial.py b/tests/test_state/test_trial.py index a433a917..562bf2b3 100644 --- a/tests/test_state/test_trial.py +++ b/tests/test_state/test_trial.py @@ -117,7 +117,7 @@ def test_trial_as_success_after_being_progress() -> None: previous_trial = "0" sampling_worker_id = "42" evaluating_worker_id = "43" - loss = 427 + objective_to_minimize = 427 cost = -123.6 extra = {"picnic": "basket", "counts": [1, 2, 3]} @@ -134,7 +134,7 @@ def test_trial_as_success_after_being_progress() -> None: trial.set_evaluating(time_started=time_started, worker_id=evaluating_worker_id) report = trial.set_complete( report_as="success", - loss=loss, + objective_to_minimize=objective_to_minimize, cost=cost, err=None, tb=None, @@ -162,7 +162,7 @@ def test_trial_as_success_after_being_progress() -> None: ) assert report == Trial.Report( trial_id=trial_id, - loss=loss, + objective_to_minimize=objective_to_minimize, cost=cost, learning_curve=None, evaluation_duration=1, @@ -173,7 +173,7 @@ def test_trial_as_success_after_being_progress() -> None: ) -def test_trial_as_failed_with_nan_loss_and_in_cost() -> None: +def test_trial_as_failed_with_nan_objective_to_minimize_and_in_cost() -> None: trial_id = "1" time_sampled = 0 time_submitted = 1 @@ -182,7 +182,7 @@ def test_trial_as_failed_with_nan_loss_and_in_cost() -> None: previous_trial = "0" sampling_worker_id = "42" evaluating_worker_id = "43" - loss = np.nan + objective_to_minimize = np.nan cost = np.inf extra = {"picnic": "basket", "counts": [1, 2, 3]} @@ -199,7 +199,7 @@ def test_trial_as_failed_with_nan_loss_and_in_cost() -> None: trial.set_evaluating(time_started=time_started, worker_id=evaluating_worker_id) report = trial.set_complete( report_as="failed", - loss=loss, + objective_to_minimize=objective_to_minimize, cost=cost, learning_curve=None, evaluation_duration=time_end - time_started, @@ -226,7 +226,7 @@ def test_trial_as_failed_with_nan_loss_and_in_cost() -> None: ) assert report == Trial.Report( trial_id=trial_id, - loss=loss, + objective_to_minimize=objective_to_minimize, cost=cost, learning_curve=None, evaluation_duration=time_end - time_started, @@ -263,7 +263,7 @@ def test_trial_as_crashed_with_err_and_tb() -> None: trial.set_evaluating(time_started=time_started, worker_id=evaluating_worker_id) report = trial.set_complete( report_as="crashed", - loss=None, + objective_to_minimize=None, cost=None, learning_curve=None, evaluation_duration=time_end - time_started, @@ -291,7 +291,7 @@ def test_trial_as_crashed_with_err_and_tb() -> None: ) assert report == Trial.Report( trial_id=trial_id, - loss=None, + objective_to_minimize=None, cost=None, learning_curve=None, evaluation_duration=time_end - time_started, 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 index c504c112..b1d18fc3 100644 --- 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 @@ -1,16 +1,21 @@ import argparse +from warnings import warn import neps import numpy as np def run_pipeline_constant(learning_rate, optimizer, epochs, batch_size): - """func for test loading of run_pipeline""" + warn("run_pipeline is deprecated, use evaluate_pipeline instead", DeprecationWarning) + return evaluate_pipeline_constant(learning_rate, optimizer, epochs, batch_size) + +def evaluate_pipeline_constant(learning_rate, optimizer, epochs, batch_size): + """func for test loading of evaluate_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} + return {"objective_to_minimize": eval_score} if __name__ == "__main__": @@ -18,10 +23,10 @@ def run_pipeline_constant(learning_rate, optimizer, epochs, batch_size): 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") + parser.add_argument("--evaluate_pipeline", action="store_true") args = parser.parse_args() - if args.run_pipeline: - neps.run(run_args=args.run_args, run_pipeline=run_pipeline_constant) + if args.evaluate_pipeline: + neps.run(run_args=args.run_args, evaluate_pipeline=evaluate_pipeline_constant) else: neps.run(run_args=args.run_args) 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 f44e6e71..a59f7739 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 @@ -1,21 +1,32 @@ +from warnings import warn import numpy as np def run_pipeline(learning_rate, optimizer, epochs): """func for test loading of run_pipeline""" + warn("run_pipeline is deprecated, use evaluate_pipeline instead", DeprecationWarning) + return evaluate_pipeline(learning_rate, optimizer, epochs) + +def evaluate_pipeline(learning_rate, optimizer, epochs): + """func for test loading of evaluate_pipeline""" if optimizer == "a": eval_score = np.random.choice([learning_rate, epochs], 1) else: eval_score = 5.0 - return {"loss": eval_score} + return {"objective_to_minimize": eval_score} def run_pipeline_constant(learning_rate, optimizer, epochs, batch_size): """func for test loading of run_pipeline""" + warn("run_pipeline is deprecated, use evaluate_pipeline instead", DeprecationWarning) + return evaluate_pipeline_constant(learning_rate, optimizer, epochs, batch_size) + +def evaluate_pipeline_constant(learning_rate, optimizer, epochs, batch_size): + """func for test loading of evaluate_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} + return {"objective_to_minimize": eval_score} 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 3b49ae05..4a026689 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 @@ -48,7 +48,7 @@ def test_run_with_yaml_and_run_pipeline() -> None: try: subprocess.check_call( - [sys.executable, BASE_PATH / "neps_run.py", yaml_path, "--run_pipeline"] + [sys.executable, BASE_PATH / "neps_run.py", yaml_path, "--evaluate_pipeline"] ) except subprocess.CalledProcessError as e: pytest.fail( diff --git a/tests/test_yaml_run_args/test_run_args_by_neps_run/neps_run.py b/tests/test_yaml_run_args/test_run_args_by_neps_run/neps_run.py index 404fea46..e470580c 100644 --- a/tests/test_yaml_run_args/test_run_args_by_neps_run/neps_run.py +++ b/tests/test_yaml_run_args/test_run_args_by_neps_run/neps_run.py @@ -1,3 +1,4 @@ +from warnings import warn import argparse import numpy as np import neps @@ -5,12 +6,17 @@ def run_pipeline(learning_rate, epochs, optimizer, batch_size): """func for test loading of run_pipeline""" + warn("run_pipeline is deprecated, use evaluate_pipeline instead", DeprecationWarning) + return evaluate_pipeline(learning_rate, epochs, optimizer, batch_size) + +def evaluate_pipeline(learning_rate, epochs, optimizer, batch_size): + """func for test loading of evaluate_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} + return {"objective_to_minimize": eval_score} # For testing the functionality of loading a dictionary from a YAML configuration. 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 aebd2a37..7995d953 100644 --- a/tests/test_yaml_run_args/test_yaml_run_args.py +++ b/tests/test_yaml_run_args/test_yaml_run_args.py @@ -1,3 +1,4 @@ +from warnings import warn import pytest import neps from neps.utils.run_args import get_run_args_from_yaml @@ -15,6 +16,11 @@ def run_pipeline(): """func to test loading of run_pipeline""" + warn("run_pipeline is deprecated, use evaluate_pipeline instead", DeprecationWarning) + return evaluate_pipeline() + +def evaluate_pipeline(): + """func to test loading of evaluate_pipeline""" return @@ -74,7 +80,7 @@ def are_functions_equivalent( # Compare keys with a function/list of functions as their values # Special because they include a module loading procedure by a path and the name of # the function - for special_key in ["run_pipeline", "pre_load_hooks"]: + for special_key in ["evaluate_pipeline", "pre_load_hooks"]: if special_key in expected_output: func_expected = expected_output.pop(special_key) func_output = output.pop(special_key) @@ -99,7 +105,7 @@ def are_functions_equivalent( ( "run_args_full.yaml", { - "run_pipeline": run_pipeline, + "evaluate_pipeline": evaluate_pipeline, "pipeline_space": pipeline_space, "root_directory": "test_yaml", "max_evaluations_total": 20, @@ -110,7 +116,7 @@ def are_functions_equivalent( "task_id": 4, "max_evaluations_per_run": 5, "continue_until_max_evaluation_completed": True, - "loss_value_on_error": 4.2, + "objective_to_minimize_value_on_error": 4.2, "cost_value_on_error": 3.7, "ignore_errors": True, "searcher": { @@ -123,7 +129,7 @@ def are_functions_equivalent( ( "run_args_full_same_level.yaml", { - "run_pipeline": run_pipeline, + "evaluate_pipeline": evaluate_pipeline, "pipeline_space": pipeline_space, "root_directory": "test_yaml", "max_evaluations_total": 20, @@ -134,7 +140,7 @@ def are_functions_equivalent( "task_id": 2.0, "max_evaluations_per_run": 5, "continue_until_max_evaluation_completed": True, - "loss_value_on_error": 2.4, + "objective_to_minimize_value_on_error": 2.4, "cost_value_on_error": 2.1, "ignore_errors": False, "searcher": { @@ -175,7 +181,7 @@ def are_functions_equivalent( ( "run_args_optional_loading_format.yaml", { - "run_pipeline": run_pipeline, + "evaluate_pipeline": evaluate_pipeline, "pipeline_space": pipeline_space, "root_directory": "test_yaml", "max_evaluations_total": 20, @@ -185,7 +191,7 @@ def are_functions_equivalent( "development_stage_id": 9, "max_evaluations_per_run": 5, "continue_until_max_evaluation_completed": True, - "loss_value_on_error": 2.4, + "objective_to_minimize_value_on_error": 2.4, "cost_value_on_error": 2.1, "ignore_errors": False, "searcher": BayesianOptimization, diff --git a/tests/test_yaml_search_space/test_search_space.py b/tests/test_yaml_search_space/test_search_space.py index bfc1c84c..449b7674 100644 --- a/tests/test_yaml_search_space/test_search_space.py +++ b/tests/test_yaml_search_space/test_search_space.py @@ -43,11 +43,11 @@ def test_correct_including_priors_yaml_file(): BASE_PATH + "correct_config_including_priors.yml" ) assert isinstance(pipeline_space, dict) - float1 = Float(0.00001, 0.1, log=True, is_fidelity=False, default=3.3e-2, default_confidence="high") + float1 = Float(0.00001, 0.1, log=True, is_fidelity=False, prior=3.3e-2, prior_confidence="high") assert float1.__eq__(pipeline_space["learning_rate"]) is True int1 = Integer(3, 30, log=False, is_fidelity=True) assert int1.__eq__(pipeline_space["num_epochs"]) is True - cat1 = Categorical(["adam", 90e-3, "rmsprop"], default=90e-3, default_confidence="medium") + cat1 = Categorical(["adam", 90e-3, "rmsprop"], prior=90e-3, prior_confidence="medium") assert cat1.__eq__(pipeline_space["optimizer"]) is True const1 = Constant(1e3) assert const1.__eq__(pipeline_space["dropout_rate"]) is True