Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion LICENSE.txt → LICENSE
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down
2 changes: 2 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down
36 changes: 29 additions & 7 deletions src/dehb/optimizers/dehb.py
Original file line number Diff line number Diff line change
Expand Up @@ -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):
Expand Down Expand Up @@ -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
Expand All @@ -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
Expand Down Expand Up @@ -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"],
Expand Down Expand Up @@ -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"]
Expand Down Expand Up @@ -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,
),
)

Expand Down Expand Up @@ -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}")
Expand Down
Loading