diff --git a/LICENSE.txt b/LICENSE similarity index 99% rename from LICENSE.txt rename to LICENSE index d60ed71..5bad67a 100644 --- a/LICENSE.txt +++ b/LICENSE @@ -186,7 +186,7 @@ same "printed page" as the copyright notice for easier identification within third-party archives. - Copyright [yyyy] [name of copyright owner] + Copyright [2021] [AutoML Freiburg] Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. diff --git a/README.md b/README.md index 3d7df39..4831d50 100644 --- a/README.md +++ b/README.md @@ -7,6 +7,8 @@ [![Static Badge](https://img.shields.io/badge/python-3.8%20%7C%203.9%20%7C%203.10%20%7C%203.11%20-blue)](https://pypi.org/project/dehb/) [![arXiv](https://img.shields.io/badge/arXiv-2105.09821-b31b1b.svg)](https://arxiv.org/abs/2105.09821) +> **_NOTE:_** Following the release of v0.1.2, this project will be maintained for stability and compatibility purposes but will no longer undergo active development. While new features will not be added, the repository will continue to receive necessary updates to ensure ongoing functionality. For any feature requests, please feel free to fork the repository and submit a pull request. + Welcome to DEHB, an algorithm for Hyperparameter Optimization (HPO). DEHB uses Differential Evolution (DE) under-the-hood as an Evolutionary Algorithm to power the black-box optimization that HPO problems pose. `dehb` is a python package implementing the [DEHB](https://arxiv.org/abs/2105.09821) algorithm. It offers an intuitive interface to optimize user-defined problems using DEHB. diff --git a/src/dehb/optimizers/dehb.py b/src/dehb/optimizers/dehb.py index 9968518..cadba8c 100644 --- a/src/dehb/optimizers/dehb.py +++ b/src/dehb/optimizers/dehb.py @@ -269,6 +269,7 @@ def __getstate__(self): d = dict(self.__dict__) d["client"] = None # hack to allow Dask client to be a class attribute d["logger"] = None # hack to allow logger object to be a class attribute + d["_runtime_budget_timer"] = None # hack to allow timer object to be a class attribute return d def __del__(self): @@ -832,7 +833,7 @@ def _save_incumbent(self): res = {} if self.use_configspace: config = self.vector_to_configspace(self.inc_config) - res["config"] = config.get_dictionary() + res["config"] = dict(config) else: res["config"] = self.inc_config.tolist() res["score"] = self.inc_score @@ -849,8 +850,11 @@ def _save_history(self, name="history.parquet.gzip"): return try: history_path = self.output_path / name - history_df = pd.DataFrame(self.history, columns=["config_id", "config", "fitness", - "cost", "fidelity", "info"]) + # Persist bracket_id to reconstruct serial replay order later + history_df = pd.DataFrame( + self.history, + columns=["bracket_id", "config_id", "config", "fitness", "cost", "fidelity", "info"], + ) # Check if the 'info' column is empty or contains only None values if history_df["info"].apply(lambda x: (isinstance(x, dict) and len(x) == 0)).all(): # Drop the 'info' column @@ -935,12 +939,20 @@ def _load_checkpoint(self, run_dir: str): history_path = run_dir / "history.parquet.gzip" history = pd.read_parquet(history_path) - # Replay history + # Sort history to emulate serial execution order if bracket_id available + if "bracket_id" in history.columns: + history = history.sort_values(by=["bracket_id", "fidelity", "config_id"]).reset_index(drop=True) + else: + # Fallback ordering for older checkpoints + history = history.sort_values(by=["fidelity", "config_id"]).reset_index(drop=True) + + # Replay history in the chosen order for _, row in history.iterrows(): job_info = { "fidelity": row["fidelity"], "config_id": row["config_id"], "config": np.array(row["config"]), + **({"bracket_id": int(row["bracket_id"]) } if "bracket_id" in history.columns else {}), } result = { "fitness": row["fitness"], @@ -992,6 +1004,8 @@ def tell(self, job_info: dict, result: dict, replay: bool=False) -> None: job_info_container["fidelity"] = job_info["fidelity"] job_info_container["config"] = job_info["config"] job_info_container["config_id"] = job_info["config_id"] + if "bracket_id" in job_info: + job_info_container["bracket_id"] = job_info["bracket_id"] # Update entry in ConfigRepository self.config_repository.configs[job_info["config_id"]].config = job_info["config"] @@ -1035,8 +1049,16 @@ def tell(self, job_info: dict, result: dict, replay: bool=False) -> None: inc_changed = True # book-keeping self._update_trackers( - traj=self.inc_score, runtime=cost, history=( - config_id, config.tolist(), float(fitness), float(cost), float(fidelity), info, + traj=self.inc_score, + runtime=cost, + history=( + bracket_id, + config_id, + config.tolist(), + float(fitness), + float(cost), + float(fidelity), + info, ), ) @@ -1166,7 +1188,7 @@ def run(self, fevals=None, brackets=None, total_cost=None, single_node_with_gpus self.logger.info("Incumbent config: ") if self.use_configspace: config = self.vector_to_configspace(self.inc_config) - for k, v in config.get_dictionary().items(): + for k, v in dict(config).items(): self.logger.info(f"{k}: {v}") else: self.logger.info(f"{self.inc_config}")