Skip to content

Commit 9439b97

Browse files
authored
Merge pull request #135 from automl/feature/multiple-seeds
Feature/multiple seeds
2 parents 5814360 + b59fca5 commit 9439b97

Some content is hidden

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

53 files changed

+20479
-16175
lines changed

CHANGELOG.md

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,13 +8,17 @@
88

99
## Enhancements
1010
- Fix lower bounds of dependency versions.
11-
- Allow to load multi-objective SMAC3v2 and add example (#69)
11+
- Allow to load multi-objective SMAC3v2 and add example (#69).
12+
- Allow to load runs with multiple seeds and add examples (#70).
13+
- Correct incumbent calculation when single objective should be maximized.
14+
- Correct range of configuration cube slider for number of configs.
1215
- Do not disable existing loggers.
1316
- Update author email.
1417
- Add exit button which first deletes running jobs and then terminates DeepCave.
1518
- Nicer handling of Keyboard Interrupt.
1619
- Disable debug mode.
1720
- Save plotly plots in higher resolution upon download.
21+
- Get hovertext per budget in Footprint, Config Cube, Cost over Time, and Pareto Front.
1822

1923
## Bug-Fixes
2024
- Fix missing objective specification in LPI evaluator (#71).
@@ -29,6 +33,7 @@
2933
- For PCP, show hyperparameters with highest importance closest to the cost, i.e. right (#124).
3034
- Add init files to all test directories.
3135
- Correct LPI importance tests.
36+
- Free port when exiting via the exit button (#52).
3237

3338
## Documentation
3439
- Add How to Contribute section.

deepcave/constants.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,3 +23,4 @@
2323
RANDOM_CONFIG_ID = -2 # Used for random configs
2424
COMBINED_COST_NAME = "Combined Cost"
2525
COMBINED_BUDGET = -1
26+
COMBINED_SEED = -1

deepcave/evaluators/footprint.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -241,7 +241,7 @@ def get_surface(
241241
Steps to create the meshgrid. By default 0.5.
242242
performance : bool, optional
243243
Whether to get the surface from the performance or the valid areas.
244-
Default is set to True.
244+
Default is set to True (i.e. from performance).
245245
246246
Returns
247247
-------

deepcave/layouts/header.py

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,10 +17,11 @@
1717
import time
1818

1919
import dash_bootstrap_components as dbc
20+
import requests
2021
from dash import dcc, html
2122
from dash.dependencies import Input, Output
2223

23-
from deepcave import app, c, queue
24+
from deepcave import app, c, config, queue
2425
from deepcave.layouts import Layout
2526

2627

@@ -114,6 +115,7 @@ def callback(n_clicks: Optional[int]) -> None:
114115
# Then we want to terminate DeepCAVE
115116
if n_clicks is not None:
116117
time.sleep(1)
118+
requests.post(f"http://localhost:{config.DASH_PORT}/shutdown")
117119
os._exit(130)
118120

119121
def __call__(self) -> html.Header: # noqa: D102

deepcave/plugins/budget/budget_correlation.py

Lines changed: 13 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -171,17 +171,25 @@ def process(run: AbstractRun, inputs: Dict[str, int]) -> Dict[str, Any]:
171171
budget2 = run.get_budget(budget2_id)
172172
budget2_readable = run.get_budget(budget2_id, human=True)
173173

174-
costs1 = run.get_all_costs(budget1, statuses=[Status.SUCCESS])
175-
costs2 = run.get_all_costs(budget2, statuses=[Status.SUCCESS])
174+
config_ids1 = run.get_configs(budget1, statuses=[Status.SUCCESS]).keys()
175+
config_ids2 = run.get_configs(budget2, statuses=[Status.SUCCESS]).keys()
176176

177177
# Combine config ids
178178
# So it is guaranteed that there is the same number of configs for each budget
179-
config_ids = set(costs1.keys()) & set(costs2.keys())
179+
config_ids = set(config_ids1) & set(config_ids2)
180180

181181
c1, c2 = [], []
182182
for config_id in config_ids:
183-
c1 += [costs1[config_id][objective_id]]
184-
c2 += [costs2[config_id][objective_id]]
183+
c1 += [
184+
run.get_avg_costs(config_id, budget1, statuses=[Status.SUCCESS])[0][
185+
objective_id
186+
]
187+
]
188+
c2 += [
189+
run.get_avg_costs(config_id, budget2, statuses=[Status.SUCCESS])[0][
190+
objective_id
191+
]
192+
]
185193

186194
correlation = round(stats.spearmanr(c1, c2).correlation, 2)
187195
correlations_symmetric["Budget"][budget2_readable] = budget2_readable # type: ignore # noqa: E501

deepcave/plugins/objective/configuration_cube.py

Lines changed: 11 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -211,10 +211,10 @@ def load_dependency_inputs(self, run, _, inputs) -> Dict[str, Any]: # type: ign
211211
budget = run.get_budget(budget_value)
212212
configs = run.get_configs(budget=budget)
213213
if n_configs_value == 0:
214-
n_configs_value = len(configs) - 1
214+
n_configs_value = len(configs)
215215
else:
216-
if n_configs_value > len(configs) - 1:
217-
n_configs_value = len(configs) - 1
216+
if n_configs_value > len(configs):
217+
n_configs_value = len(configs)
218218

219219
# Restrict to three hyperparameters
220220
selected_hps = inputs["hyperparameter_names"]["value"]
@@ -233,8 +233,8 @@ def load_dependency_inputs(self, run, _, inputs) -> Dict[str, Any]: # type: ign
233233
},
234234
"n_configs": {
235235
"min": 0,
236-
"max": len(configs) - 1,
237-
"marks": get_slider_marks(list(range(len(configs)))),
236+
"max": len(configs),
237+
"marks": get_slider_marks(list(range(0, len(configs) + 1))),
238238
"value": n_configs_value,
239239
},
240240
"hyperparameter_names": {
@@ -332,6 +332,9 @@ def load_outputs(run, inputs, outputs) -> go.Figure: # type: ignore
332332
n_configs = inputs["n_configs"]
333333
objective_id = inputs["objective_id"]
334334
objective = run.get_objective(objective_id)
335+
budget = run.get_budget(inputs["budget_id"])
336+
df = df.groupby(df.columns.drop(objective.name).to_list(), as_index=False).mean()
337+
df.index = df.index.astype("str")
335338

336339
# Limit to n_configs
337340
idx = [str(i) for i in range(n_configs, len(df))]
@@ -397,7 +400,9 @@ def load_outputs(run, inputs, outputs) -> go.Figure: # type: ignore
397400
"color": costs,
398401
"colorbar": {"thickness": 30, "title": objective.name},
399402
},
400-
"hovertext": [get_hovertext_from_config(run, config_id) for config_id in config_ids],
403+
"hovertext": [
404+
get_hovertext_from_config(run, config_id, budget) for config_id in config_ids
405+
],
401406
"meta": {"colorbar": costs},
402407
"hoverinfo": "text",
403408
}

deepcave/plugins/objective/cost_over_time.py

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -345,6 +345,7 @@ def load_outputs(runs, inputs, outputs) -> go.Figure: # type: ignore
345345
continue
346346

347347
objective = run.get_objective(inputs["objective_id"])
348+
budget = run.get_budget(inputs["budget_id"])
348349
config_ids = outputs[run.id]["config_ids"]
349350
x = outputs[run.id]["times"]
350351
if inputs["xaxis"] == "trials":
@@ -361,7 +362,9 @@ def load_outputs(runs, inputs, outputs) -> go.Figure: # type: ignore
361362
symbol = None
362363
mode = "lines"
363364
if len(config_ids) > 0:
364-
hovertext = [get_hovertext_from_config(run, config_id) for config_id in config_ids]
365+
hovertext = [
366+
get_hovertext_from_config(run, config_id, budget) for config_id in config_ids
367+
]
365368
hoverinfo = "text"
366369
symbol = "circle"
367370
mode = "lines+markers"

deepcave/plugins/objective/parallel_coordinates.py

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -290,7 +290,9 @@ def process(run, inputs) -> Dict[str, Any]: # type: ignore
290290
"""
291291
budget = run.get_budget(inputs["budget_id"])
292292
objective = run.get_objective(inputs["objective_id"])
293-
df = serialize(run.get_encoded_data(objective, budget))
293+
df = run.get_encoded_data(objective, budget)
294+
df = df.groupby(df.columns.drop(objective.name).to_list(), as_index=False).mean()
295+
df = serialize(df)
294296
result: Dict[str, Any] = {"df": df}
295297

296298
if inputs["show_important_only"]:

deepcave/plugins/objective/pareto_front.py

Lines changed: 91 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -183,20 +183,38 @@ def get_filter_layout(register: Callable) -> List[Any]:
183183
The layouts for the filter block.
184184
"""
185185
return [
186-
html.Div(
186+
dbc.Row(
187187
[
188-
dbc.Label("Show all configurations"),
189-
help_button(
190-
"Additionally to the pareto front, also the other configurations "
191-
"are displayed. This makes it easier to see the performance "
192-
"differences."
188+
dbc.Col(
189+
[
190+
dbc.Label("Show all configurations"),
191+
help_button(
192+
"Additionally to the pareto front, also the other configurations "
193+
"are displayed. This makes it easier to see the performance "
194+
"differences."
195+
),
196+
dbc.Select(
197+
id=register("show_all", ["value", "options"]),
198+
placeholder="Select ...",
199+
),
200+
],
201+
md=6,
193202
),
194-
dbc.Select(
195-
id=register("show_all", ["value", "options"]),
196-
placeholder="Select ...",
203+
dbc.Col(
204+
[
205+
dbc.Label("Show error bars"),
206+
help_button(
207+
"Show error bars In the case of non-deterministic runs with "
208+
"multiple seeds evaluated per configuration."
209+
),
210+
dbc.Select(
211+
id=register("show_error", ["value", "options"]),
212+
placeholder="Select ...",
213+
),
214+
],
215+
md=6,
197216
),
198217
],
199-
className="mb-3",
200218
),
201219
dbc.Row(
202220
[
@@ -251,6 +269,7 @@ def load_inputs(self) -> Dict[str, Dict[str, Any]]:
251269
"value": self.budget_options[-1]["value"],
252270
},
253271
"show_all": {"options": get_select_options(binary=True), "value": "false"},
272+
"show_error": {"options": get_select_options(binary=True), "value": "false"},
254273
"show_runs": {"options": get_select_options(binary=True), "value": "true"},
255274
"show_groups": {"options": get_select_options(binary=True), "value": "true"},
256275
}
@@ -291,22 +310,29 @@ def process(run, inputs) -> Dict[str, Any]: # type: ignore
291310
objective_id_2 = inputs["objective_id_2"]
292311
objective_2 = run.get_objective(objective_id_2)
293312

294-
points: Union[List, np.ndarray] = []
295-
config_ids: Union[List, np.ndarray] = []
296-
for config_id, costs in run.get_all_costs(budget, statuses=[Status.SUCCESS]).items():
297-
points += [[costs[objective_id_1], costs[objective_id_2]]]
298-
config_ids += [config_id]
313+
points_avg: Union[List, np.ndarray] = []
314+
points_std: Union[List, np.ndarray] = []
315+
config_ids: Union[List, np.ndarray] = list(
316+
run.get_configs(budget, statuses=[Status.SUCCESS]).keys()
317+
)
318+
319+
for config_id in config_ids:
320+
avg_costs, std_costs = run.get_avg_costs(config_id, budget, statuses=[Status.SUCCESS])
321+
points_avg += [[avg_costs[objective_id_1], avg_costs[objective_id_2]]]
322+
points_std += [[std_costs[objective_id_1], std_costs[objective_id_2]]]
299323

300-
points = np.array(points)
324+
points_avg = np.array(points_avg)
325+
points_std = np.array(points_std)
301326
config_ids = np.array(config_ids)
302327

303328
# Sort the points s.t. x axis is monotonically increasing
304-
sorted_idx = np.argsort(points[:, 0])
305-
points = points[sorted_idx]
329+
sorted_idx = np.argsort(points_avg[:, 0])
330+
points_avg = points_avg[sorted_idx]
331+
points_std = points_std[sorted_idx]
306332
config_ids = config_ids[sorted_idx]
307333

308-
is_front: np.ndarray = np.ones(points.shape[0], dtype=bool)
309-
for point_idx, costs in enumerate(points):
334+
is_front: np.ndarray = np.ones(points_avg.shape[0], dtype=bool)
335+
for point_idx, costs in enumerate(points_avg):
310336
if is_front[point_idx]:
311337
# Keep any point with a lower/upper cost
312338
# This loop is a little bit complicated than
@@ -316,9 +342,9 @@ def process(run, inputs) -> Dict[str, Any]: # type: ignore
316342
select = None
317343
for idx, (objective, cost) in enumerate(zip([objective_1, objective_2], costs)):
318344
if objective.optimize == "upper":
319-
select2 = np.any(points[is_front][:, idx, np.newaxis] > [cost], axis=1)
345+
select2 = np.any(points_avg[is_front][:, idx, np.newaxis] > [cost], axis=1)
320346
else:
321-
select2 = np.any(points[is_front][:, idx, np.newaxis] < [cost], axis=1)
347+
select2 = np.any(points_avg[is_front][:, idx, np.newaxis] < [cost], axis=1)
322348

323349
if select is None:
324350
select = select2
@@ -331,7 +357,8 @@ def process(run, inputs) -> Dict[str, Any]: # type: ignore
331357
is_front[point_idx] = True
332358

333359
return {
334-
"points": points.tolist(),
360+
"points_avg": points_avg.tolist(),
361+
"points_std": points_std.tolist(),
335362
"pareto_points": is_front.tolist(),
336363
"config_ids": config_ids.tolist(),
337364
}
@@ -384,6 +411,7 @@ def load_outputs(runs, inputs, outputs) -> go.Figure: # type: ignore
384411
The output figure.
385412
"""
386413
show_all = inputs["show_all"]
414+
show_error = inputs["show_error"]
387415

388416
traces = []
389417
for idx, run in enumerate(runs):
@@ -396,31 +424,50 @@ def load_outputs(runs, inputs, outputs) -> go.Figure: # type: ignore
396424
if run.prefix != "group" and not show_runs:
397425
continue
398426

399-
points = np.array(outputs[run.id]["points"])
427+
points_avg = np.array(outputs[run.id]["points_avg"])
428+
points_std = np.array(outputs[run.id]["points_std"])
400429
config_ids = outputs[run.id]["config_ids"]
430+
budget = run.get_budget(inputs["budget_id"])
401431
pareto_config_ids = []
402432

403-
x, y = [], []
404-
x_pareto, y_pareto = [], []
433+
x, y, x_std, y_std = [], [], [], []
434+
x_pareto, y_pareto, x_pareto_std, y_pareto_std = [], [], [], []
405435

406436
pareto_points = outputs[run.id]["pareto_points"]
407437
for point_idx, pareto in enumerate(pareto_points):
408438
if pareto:
409-
x_pareto += [points[point_idx][0]]
410-
y_pareto += [points[point_idx][1]]
439+
x_pareto += [points_avg[point_idx][0]]
440+
y_pareto += [points_avg[point_idx][1]]
441+
x_pareto_std += [points_std[point_idx][0]]
442+
y_pareto_std += [points_std[point_idx][1]]
411443
pareto_config_ids += [config_ids[point_idx]]
412444
else:
413-
x += [points[point_idx][0]]
414-
y += [points[point_idx][1]]
445+
x += [points_avg[point_idx][0]]
446+
y += [points_avg[point_idx][1]]
447+
x_std += [points_std[point_idx][0]]
448+
y_std += [points_std[point_idx][1]]
415449

416450
color = get_color(idx, alpha=0.1)
417451
color_pareto = get_color(idx)
418452

419453
if show_all:
454+
error_x = (
455+
dict(array=x_std, color="rgba(0, 0, 0, 0.3)")
456+
if show_error and not all(value == 0.0 for value in x_std)
457+
else None
458+
)
459+
error_y = (
460+
dict(array=y_std, color="rgba(0, 0, 0, 0.3)")
461+
if show_error and not all(value == 0.0 for value in y_std)
462+
else None
463+
)
464+
420465
traces.append(
421466
go.Scatter(
422467
x=x,
423468
y=y,
469+
error_x=error_x,
470+
error_y=error_y,
424471
name=run.name,
425472
mode="markers",
426473
showlegend=False,
@@ -444,13 +491,26 @@ def load_outputs(runs, inputs, outputs) -> go.Figure: # type: ignore
444491
line_shape = "hv"
445492

446493
hovertext = [
447-
get_hovertext_from_config(run, config_id) for config_id in pareto_config_ids
494+
get_hovertext_from_config(run, config_id, budget) for config_id in pareto_config_ids
448495
]
449496

497+
error_pareto_x = (
498+
dict(array=x_pareto_std, color="rgba(0, 0, 0, 0.3)")
499+
if show_error and not all(value == 0.0 for value in x_pareto_std)
500+
else None
501+
)
502+
error_pareto_y = (
503+
dict(array=y_pareto_std, color="rgba(0, 0, 0, 0.3)")
504+
if show_error and not all(value == 0.0 for value in y_pareto_std)
505+
else None
506+
)
507+
450508
traces.append(
451509
go.Scatter(
452510
x=x_pareto,
453511
y=y_pareto,
512+
error_x=error_pareto_x,
513+
error_y=error_pareto_y,
454514
name=run.name,
455515
line_shape=line_shape,
456516
showlegend=True,

0 commit comments

Comments
 (0)