Skip to content

Commit 1f29ef7

Browse files
authored
Merge pull request #36 from automl/feature/add-converter-for-smac-2.0
Feature/add converter for smac 2.0
2 parents dcf1239 + 3e62216 commit 1f29ef7

File tree

98 files changed

+22003
-37
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

98 files changed

+22003
-37
lines changed

.pre-commit-config.yaml

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
# If things break on update, raise an issue
44
repos:
55
- repo: https://github.com/pycqa/isort
6-
rev: 5.10.1
6+
rev: 5.12.0
77
hooks:
88
- id: isort
99
name: isort imports deepcave
@@ -14,7 +14,7 @@ repos:
1414
files: tests
1515

1616
- repo: https://github.com/ambv/black
17-
rev: 22.1.0
17+
rev: 23.1.0
1818
hooks:
1919
- id: black
2020
name: black formatter deepcave
@@ -29,7 +29,7 @@ repos:
2929
files: examples
3030

3131
- repo: https://github.com/pycqa/pydocstyle
32-
rev: 6.1.1
32+
rev: 6.3.0
3333
hooks:
3434
- id: pydocstyle
3535
files: deepcave
@@ -42,8 +42,8 @@ repos:
4242
name: mypy deepcave
4343
files: deepcave
4444

45-
- repo: https://gitlab.com/pycqa/flake8
46-
rev: 4.0.1
45+
- repo: https://github.com/PyCQA/flake8
46+
rev: 6.0.0
4747
hooks:
4848
- id: flake8
4949
name: flake8 deepcave

CHANGELOG.md

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,11 @@
1+
# Version 1.1
2+
3+
## Converters
4+
- SMAC 2.0
5+
6+
## Dependencies
7+
- Remove SMAC dependency by adding required function directly
8+
19
# Version 1.0.1
210

311
## Bug-Fixes

deepcave/__init__.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@
1717
"Source Code": "https://github.com/automl/deepcave",
1818
}
1919
copyright = f"Copyright {datetime.date.today().strftime('%Y')}, {author}"
20-
version = "1.0.1"
20+
version = "1.1"
2121

2222
_exec_file = sys.argv[0]
2323
_exec_files = ["server.py", "worker.py", "sphinx-build"]

deepcave/config.py

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -83,6 +83,7 @@ def PLUGINS(self) -> Dict[str, List["Plugin"]]:
8383
def CONVERTERS(self) -> List[Type["Run"]]:
8484
from deepcave.runs.converters.bohb import BOHBRun
8585
from deepcave.runs.converters.deepcave import DeepCAVERun
86-
from deepcave.runs.converters.smac import SMACRun
86+
from deepcave.runs.converters.smac3v1 import SMAC3v1Run
87+
from deepcave.runs.converters.smac3v2 import SMAC3v2Run
8788

88-
return [DeepCAVERun, BOHBRun, SMACRun]
89+
return [DeepCAVERun, BOHBRun, SMAC3v1Run, SMAC3v2Run]

deepcave/evaluators/epm/random_forest.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,8 @@
1616
from sklearn.decomposition import PCA
1717
from sklearn.exceptions import NotFittedError
1818
from sklearn.preprocessing import MinMaxScaler
19-
from smac.epm.util_funcs import get_types
19+
20+
from deepcave.evaluators.epm.utils import get_types
2021

2122
VERY_SMALL_NUMBER = 1e-10
2223
PYRFR_MAPPING = {

deepcave/evaluators/epm/utils.py

Lines changed: 117 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,117 @@
1+
import typing
2+
3+
import numpy as np
4+
from ConfigSpace import ConfigurationSpace
5+
from ConfigSpace.hyperparameters import (
6+
BetaFloatHyperparameter,
7+
BetaIntegerHyperparameter,
8+
CategoricalHyperparameter,
9+
Constant,
10+
NormalFloatHyperparameter,
11+
NormalIntegerHyperparameter,
12+
OrdinalHyperparameter,
13+
UniformFloatHyperparameter,
14+
UniformIntegerHyperparameter,
15+
)
16+
17+
18+
def get_types(
19+
config_space: ConfigurationSpace,
20+
instance_features: typing.Optional[np.ndarray] = None,
21+
) -> typing.Tuple[typing.List[int], typing.List[typing.Tuple[float, float]]]:
22+
"""Return the types of the hyperparameters and the bounds of the
23+
hyperparameters and instance features.
24+
"""
25+
# Extract types vector for rf from config space and the bounds
26+
types = [0] * len(config_space.get_hyperparameters())
27+
bounds = [(np.nan, np.nan)] * len(types)
28+
29+
for i, param in enumerate(config_space.get_hyperparameters()):
30+
parents = config_space.get_parents_of(param.name)
31+
if len(parents) == 0:
32+
can_be_inactive = False
33+
else:
34+
can_be_inactive = True
35+
36+
if isinstance(param, (CategoricalHyperparameter)):
37+
n_cats = len(param.choices)
38+
if can_be_inactive:
39+
n_cats = len(param.choices) + 1
40+
types[i] = n_cats
41+
bounds[i] = (int(n_cats), np.nan)
42+
elif isinstance(param, (OrdinalHyperparameter)):
43+
n_cats = len(param.sequence)
44+
types[i] = 0
45+
if can_be_inactive:
46+
bounds[i] = (0, int(n_cats))
47+
else:
48+
bounds[i] = (0, int(n_cats) - 1)
49+
elif isinstance(param, Constant):
50+
# for constants we simply set types to 0 which makes it a numerical
51+
# parameter
52+
if can_be_inactive:
53+
bounds[i] = (2, np.nan)
54+
types[i] = 2
55+
else:
56+
bounds[i] = (0, np.nan)
57+
types[i] = 0
58+
# and we leave the bounds to be 0 for now
59+
elif isinstance(param, UniformFloatHyperparameter):
60+
# Are sampled on the unit hypercube thus the bounds
61+
# are always 0.0, 1.0
62+
if can_be_inactive:
63+
bounds[i] = (-1.0, 1.0)
64+
else:
65+
bounds[i] = (0, 1.0)
66+
elif isinstance(param, UniformIntegerHyperparameter):
67+
if can_be_inactive:
68+
bounds[i] = (-1.0, 1.0)
69+
else:
70+
bounds[i] = (0, 1.0)
71+
elif isinstance(param, NormalFloatHyperparameter):
72+
if can_be_inactive:
73+
raise ValueError(
74+
"Inactive parameters not supported for Beta and Normal Hyperparameters"
75+
)
76+
77+
bounds[i] = (param._lower, param._upper)
78+
elif isinstance(param, NormalIntegerHyperparameter):
79+
if can_be_inactive:
80+
raise ValueError(
81+
"Inactive parameters not supported for Beta and Normal Hyperparameters"
82+
)
83+
84+
bounds[i] = (param.nfhp._lower, param.nfhp._upper)
85+
elif isinstance(param, BetaFloatHyperparameter):
86+
if can_be_inactive:
87+
raise ValueError(
88+
"Inactive parameters not supported for Beta and Normal Hyperparameters"
89+
)
90+
91+
bounds[i] = (param._lower, param._upper)
92+
elif isinstance(param, BetaIntegerHyperparameter):
93+
if can_be_inactive:
94+
raise ValueError(
95+
"Inactive parameters not supported for Beta and Normal Hyperparameters"
96+
)
97+
98+
bounds[i] = (param.bfhp._lower, param.bfhp._upper)
99+
elif not isinstance(
100+
param,
101+
(
102+
UniformFloatHyperparameter,
103+
UniformIntegerHyperparameter,
104+
OrdinalHyperparameter,
105+
CategoricalHyperparameter,
106+
NormalFloatHyperparameter,
107+
NormalIntegerHyperparameter,
108+
BetaFloatHyperparameter,
109+
BetaIntegerHyperparameter,
110+
),
111+
):
112+
raise TypeError("Unknown hyperparameter type %s" % type(param))
113+
114+
if instance_features is not None:
115+
types = types + [0] * instance_features.shape[1]
116+
117+
return types, bounds

deepcave/runs/__init__.py

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -482,7 +482,6 @@ def get_status(self, config_id: int, budget: Optional[Union[int, float]] = None)
482482
# Unfortunately, we have to iterate through the history to find the status
483483
# TODO: Cache the stati
484484
for trial in self.history:
485-
486485
if trial_key == trial.get_key():
487486
return trial.status
488487

@@ -893,7 +892,7 @@ def get_encoded_data(
893892

894893
def check_equality(
895894
runs: List[AbstractRun],
896-
meta: bool = True,
895+
meta: bool = False,
897896
configspace: bool = True,
898897
objectives: bool = True,
899898
budgets: bool = True,
Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -9,8 +9,8 @@
99
from deepcave.utils.hash import file_to_hash
1010

1111

12-
class SMACRun(Run):
13-
prefix = "SMAC"
12+
class SMAC3v1Run(Run):
13+
prefix = "SMAC3v1"
1414
_initial_order = 2
1515

1616
@property
@@ -65,7 +65,7 @@ def from_path(cls, path):
6565
meta[arg] = value
6666

6767
# Let's create a new run object
68-
run = SMACRun(
68+
run = SMAC3v1Run(
6969
name=path.stem, configspace=configspace, objectives=[objective1, objective2], meta=meta
7070
)
7171

Lines changed: 147 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,147 @@
1+
import json
2+
from pathlib import Path
3+
4+
import numpy as np
5+
6+
from deepcave.runs import Status
7+
from deepcave.runs.objective import Objective
8+
from deepcave.runs.run import Run
9+
from deepcave.utils.hash import file_to_hash
10+
11+
12+
class SMAC3v2Run(Run):
13+
prefix = "SMAC3v2"
14+
_initial_order = 2
15+
16+
@property
17+
def hash(self):
18+
if self.path is None:
19+
return ""
20+
21+
# Use hash of history.json as id
22+
return file_to_hash(self.path / "runhistory.json")
23+
24+
@property
25+
def latest_change(self):
26+
if self.path is None:
27+
return 0
28+
29+
return Path(self.path / "runhistory.json").stat().st_mtime
30+
31+
@classmethod
32+
def from_path(cls, path):
33+
"""
34+
Based on working_dir/run_name/*, return a new trials object.
35+
"""
36+
path = Path(path)
37+
38+
# Read configspace
39+
from ConfigSpace.read_and_write import json as cs_json
40+
41+
with (path / "configspace.json").open("r") as f:
42+
configspace = cs_json.read(f.read())
43+
44+
# Read objectives
45+
# We have to define it ourselves, because we don't know the type of the objective
46+
# Only lock lower
47+
objective1 = Objective("Cost", lower=0)
48+
objective2 = Objective("Time", lower=0)
49+
50+
# Read meta
51+
with (path / "scenario.json").open() as json_file:
52+
meta = json.load(json_file)
53+
meta["run_objectives"] = meta.pop("objectives")
54+
55+
# Let's create a new run object
56+
run = SMAC3v2Run(
57+
name=path.stem, configspace=configspace, objectives=[objective1, objective2], meta=meta
58+
)
59+
60+
# We have to set the path manually
61+
run._path = path
62+
63+
# Iterate over the runhistory
64+
with (path / "runhistory.json").open() as json_file:
65+
all_data = json.load(json_file)
66+
data = all_data["data"]
67+
config_origins = all_data["config_origins"]
68+
configs = all_data["configs"]
69+
70+
instance_ids = []
71+
72+
first_starttime = None
73+
seeds = []
74+
for (
75+
config_id,
76+
instance_id,
77+
seed,
78+
budget,
79+
cost,
80+
time,
81+
status,
82+
starttime,
83+
endtime,
84+
additional_info,
85+
) in data:
86+
if instance_id not in instance_ids:
87+
instance_ids += [instance_id]
88+
89+
if len(instance_ids) > 1:
90+
raise RuntimeError("Instances are not supported.")
91+
92+
config_id = str(config_id)
93+
config = configs[config_id]
94+
95+
if seed not in seeds:
96+
seeds.append(seed)
97+
98+
if len(seeds) > 1:
99+
raise RuntimeError("Multiple seeds are not supported.")
100+
101+
if first_starttime is None:
102+
first_starttime = starttime
103+
104+
starttime = starttime - first_starttime
105+
endtime = endtime - first_starttime
106+
107+
if status == 0:
108+
# still running
109+
continue
110+
elif status == 1:
111+
status = Status.SUCCESS
112+
elif status == 3:
113+
status = Status.TIMEOUT
114+
elif status == 4:
115+
status = Status.MEMORYOUT
116+
else:
117+
status = Status.CRASHED
118+
119+
if status != Status.SUCCESS:
120+
# We don't want cost included which are failed
121+
cost = None
122+
time = None
123+
else:
124+
time = endtime - starttime
125+
126+
# Round budget
127+
if budget:
128+
budget = np.round(budget, 2)
129+
else:
130+
budget = 0.0
131+
132+
origin = None
133+
if config_id in config_origins:
134+
origin = config_origins[config_id]
135+
136+
run.add(
137+
costs=[cost, time],
138+
config=config,
139+
budget=budget,
140+
start_time=starttime,
141+
end_time=endtime,
142+
status=status,
143+
origin=origin,
144+
additional=additional_info,
145+
)
146+
147+
return run

0 commit comments

Comments
 (0)