Skip to content

Commit 93d21e8

Browse files
committed
2 parents 1ce84cc + 3c446ef commit 93d21e8

47 files changed

Lines changed: 4095 additions & 1345 deletions

Some content is hidden

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

README.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -97,6 +97,12 @@ cave --folders examples/smac3/example_output/* --ta_exec examples/smac3/ --outpu
9797
This will analyze the results located in `examples/smac3` in the dirs `example_output/run_1` and `example_output/run_2`.
9898
The report is located in `CAVE_results/report.html`.
9999
`--ta_exec` corresponds to the folder from which the optimizer was originally executed (used to find the necessary files for loading the `scenario`).
100+
For other formats, e.g.:
101+
```
102+
cave --folders examples/smac2/ --ta_exec_dir examples/smac2/smac-output/aclib/state-run1/ --file_format smac2 --no_algorithm_footprint
103+
cave --folders examples/csv_allinone/ --ta_exec_dir examples/csv_allinone/ --file_format csv
104+
105+
```
100106

101107
# USAGE WITH BOHB
102108
You can also use cave with Configurators that use budgets to estimate a quality of a certain algorithm (e.g. epochs in

cave/analyzer.py

Lines changed: 0 additions & 946 deletions
This file was deleted.

cave/analyzer/__init__.py

Whitespace-only changes.
Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
import os
2+
from collections import OrderedDict
3+
import logging
4+
5+
from bokeh.plotting import show
6+
from bokeh.io import output_notebook
7+
from bokeh.embed import components
8+
9+
from cave.analyzer.base_analyzer import BaseAnalyzer
10+
from cave.plot.algorithm_footprint import AlgorithmFootprintPlotter
11+
from cave.utils.timing import timing
12+
13+
class AlgorithmFootprint(BaseAnalyzer):
14+
15+
@timing
16+
def __init__(self, algorithms, epm_rh, train, test, features,
17+
cutoff, output_dir, rng, density=200, purity=0.95):
18+
self.logger = logging.getLogger(self.__module__ + '.' + self.__class__.__name__)
19+
20+
# filter instance features
21+
train_feats = {k: v for k, v in features.items() if k in train}
22+
test_feats = {k: v for k, v in features.items() if k in test}
23+
if not (train_feats or test_feats):
24+
self.logger.warning("No instance-features could be detected. "
25+
"No algorithm footprints available.")
26+
raise ValueError("Could not detect any instances.")
27+
28+
self.logger.info("... algorithm footprints for: {}".format(",".join([a[1] for a in algorithms])))
29+
footprint = AlgorithmFootprintPlotter(epm_rh,
30+
train_feats, test_feats,
31+
algorithms,
32+
cutoff,
33+
output_dir,
34+
rng=rng)
35+
# Plot footprints
36+
self.bokeh_plot = footprint.plot_interactive_footprint()
37+
self.script, self.div = components(self.bokeh_plot)
38+
self.plots3d = footprint.plot3d()
39+
40+
def get_jupyter(self):
41+
output_notebook()
42+
show(self.bokeh_plot)
43+
44+
def get_html(self, d=None):
45+
bokeh_components = self.script, self.div
46+
if d is not None:
47+
d["Algorithm Footprints"] = OrderedDict()
48+
# Interactive bokeh-plot
49+
d["Algorithm Footprints"]["Interactive Algorithm Footprint"] = {"bokeh" : (self.script, self.div)}
50+
for plots in self.plots3d:
51+
header = os.path.splitext(os.path.split(plots[0])[1])[0][10:-2]
52+
header = header[0].upper() + header[1:].replace('_', ' ')
53+
d["Algorithm Footprints"][header] = {"figure_x2": plots}
54+
55+
return bokeh_components
56+
57+
def get_plots(self):
58+
all_plots = []
59+
for plots in self.plots3d:
60+
all_plots.extend(plots)
61+
return all_plots

cave/analyzer/base_analyzer.py

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
from typing import List, Tuple
2+
3+
class BaseAnalyzer(object):
4+
5+
def __init__(self, *args, **kwargs):
6+
self.plots = []
7+
8+
def get_static_plots(self) -> List[str]:
9+
""" Returns plot-paths, if any are available
10+
11+
Returns
12+
-------
13+
plot_paths: List[str]
14+
returns list of strings
15+
"""
16+
raise NotImplementedError()
17+
18+
def get_table(self):
19+
""" Get table, if available """
20+
raise NotImplementedError()
21+
22+
def get_html(self) -> Tuple[str, str]:
23+
"""General reports in html-format, to be easily integrated in html-code. ALSO FOR BOKEH-OUTPUT.
24+
25+
Returns
26+
-------
27+
script, div: str, str
28+
header and body part of html-code
29+
"""
30+
raise NotImplementedError()
31+
32+
def get_jupyter(self):
33+
"""Depending on analysis, this creates jupyter-notebook compatible output."""
34+
raise NotImplementedError()
Lines changed: 232 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,232 @@
1+
import os
2+
from collections import OrderedDict
3+
import logging
4+
import itertools
5+
6+
from bokeh.io import output_notebook
7+
from bokeh.plotting import show, figure, ColumnDataSource
8+
from bokeh.embed import components
9+
from bokeh.models import HoverTool, ColorBar, LinearColorMapper, BasicTicker, CustomJS, Slider
10+
from bokeh.models.sources import CDSView
11+
from bokeh.models.filters import GroupFilter
12+
from bokeh.layouts import column, row, widgetbox
13+
from bokeh.models.widgets import CheckboxButtonGroup, CheckboxGroup, Button
14+
from bokeh.palettes import Spectral11
15+
16+
from cave.analyzer.base_analyzer import BaseAnalyzer
17+
from cave.utils.timing import timing
18+
19+
class BohbLearningCurves(BaseAnalyzer):
20+
21+
def __init__(self, hp_names, result_object=None, result_path=None):
22+
"""
23+
Visualize hpbandster learning curves in an interactive bokeh-plot.
24+
25+
Parameters
26+
----------
27+
hp_names: List[str]
28+
list with hyperparameters-names
29+
result_object: Result
30+
hpbandster-result object. must be specified if result_path is not
31+
result_path: str
32+
path to hpbandster result-folder. must contain configs.json and results.json. must be specified if result_object is not
33+
"""
34+
self.logger = logging.getLogger(self.__module__ + '.' + self.__class__.__name__)
35+
try:
36+
from hpbandster.core.result import logged_results_to_HBS_result
37+
from hpbandster.core.result import extract_HBS_learning_curves
38+
except ImportError as err:
39+
self.logger.exception(err)
40+
raise ImportError("You need to install hpbandster (e.g. 'pip install hpbandster') to analyze bohb-results.")
41+
42+
if (result_path and result_object) or not (result_path or result_object):
43+
raise ValueError("Specify either result_path or result_object. (currently \"%s\" and \"%s\")" % (result_path, result_object))
44+
elif result_path:
45+
result_object = logged_results_to_HBS_result(result_path)
46+
47+
incumbent_trajectory = result_object.get_incumbent_trajectory()
48+
lcs = result_object.get_learning_curves(lc_extractor=extract_HBS_learning_curves)
49+
self.bokeh_plot = self.bohb_plot(result_object, lcs, hp_names)
50+
51+
def bohb_plot(self, result_object, learning_curves, hyperparameter_names, reset_times=False):
52+
# Extract information from learning-curve-dict
53+
times, losses, config_ids, = [], [], []
54+
for conf_id, learning_curves in learning_curves.items():
55+
self.logger.debug("Config ID: %s, learning_curves: %s", str(conf_id), str(learning_curves))
56+
for l in learning_curves:
57+
if len(l) == 0:
58+
continue
59+
tmp = list(zip(*l))
60+
times.append(tmp[0])
61+
losses.append(tmp[1])
62+
config_ids.append(conf_id)
63+
64+
if reset_times:
65+
times = [np.array(ts) - ts[0] for ts in times]
66+
67+
# Prepare ColumnDataSource
68+
data = OrderedDict([
69+
('config_id', []),
70+
('config_info', []),
71+
('times', []),
72+
('losses', []),
73+
('duration', []),
74+
('HB_iteration', []),
75+
('colors', []),
76+
])
77+
for hp in hyperparameter_names:
78+
data[hp] = []
79+
80+
# Colors
81+
colors = {c_id : color for c_id, color in zip(config_ids, itertools.cycle(Spectral11))}
82+
83+
# Populate
84+
id2conf = result_object.get_id2config_mapping()
85+
for counter, c_id in enumerate(config_ids):
86+
if not (len(times[counter]) == len(losses[counter])):
87+
raise ValueError()
88+
longest_run = self.get_longest_run(c_id, result_object)
89+
if not longest_run:
90+
continue
91+
data['config_id'].append(str(c_id))
92+
try:
93+
config_info = '\n'.join([str(k) + "=" +str(v) for k,v in sorted(id2conf[c_id]['config_info'].items())])
94+
except:
95+
config_info = 'Not Available'
96+
data['config_info'].append(config_info)
97+
data['times'].append(times[counter])
98+
data['losses'].append(losses[counter])
99+
if longest_run:
100+
data['duration'].append(longest_run['time_stamps']['finished'] - longest_run['time_stamps']['started'])
101+
else:
102+
data['duration'].append('N/A')
103+
data['HB_iteration'].append(str(c_id[0]))
104+
data['colors'].append(colors[c_id])
105+
for hp in hyperparameter_names:
106+
try:
107+
data[hp].append(id2conf[c_id]['config'][hp])
108+
except KeyError:
109+
data[hp].append("None")
110+
self.logger.debug(data)
111+
112+
# Tooltips
113+
tooltips=[(key, '@' + key) for key in data.keys() if not key in ['times', 'duration', 'colors']]
114+
tooltips.insert(4, ('duration (sec)', '@duration'))
115+
hover = HoverTool(tooltips=tooltips)
116+
117+
# Create sources
118+
source_multiline = ColumnDataSource(data=data)
119+
# Special source for scattering points, since times and losses for multi_line are nested lists
120+
scatter_data = {key : [] for key in data.keys()}
121+
for idx, c_id in enumerate(data['config_id']):
122+
for t, l in zip(data['times'][idx], data['losses'][idx]):
123+
scatter_data['times'].append(t)
124+
scatter_data['losses'].append(l)
125+
for key in list(data.keys()):
126+
if key in ['times', 'losses']:
127+
continue
128+
scatter_data[key].append(data[key][idx])
129+
source_scatter = ColumnDataSource(data=scatter_data)
130+
131+
# Create plot
132+
p = figure(plot_height=500, plot_width=600,
133+
y_axis_type="log" if len([a for a in scatter_data['losses'] if a <= 0]) == 0 else 'linear',
134+
tools=[hover, 'save', 'pan', 'wheel_zoom', 'box_zoom', 'reset'])
135+
136+
# Plot per HB_iteration, each config individually
137+
HB_iterations = sorted(set(data['HB_iteration']))
138+
HB_handles = []
139+
for it in HB_iterations:
140+
line_handles = []
141+
view = CDSView(source=source_multiline, filters=[GroupFilter(column_name='HB_iteration', group=str(it))])
142+
line_handles.append(p.multi_line(xs='times', ys='losses',
143+
source=source_multiline,
144+
view=view,
145+
color='colors',
146+
line_width=5,
147+
))
148+
view = CDSView(source=source_scatter, filters=[GroupFilter(column_name='HB_iteration', group=str(it))])
149+
line_handles.append(p.circle(x='times', y='losses',
150+
source=source_scatter,
151+
view=view,
152+
fill_color='colors',
153+
line_color='colors',
154+
size=20,
155+
))
156+
HB_handles.append(line_handles)
157+
158+
# Add interactiveness (select certain lines, etc.)
159+
num_total_lines = sum([len(group) for group in HB_handles])
160+
glyph_renderers_flattened = ['glyph_renderer' + str(i) for i in range(num_total_lines)]
161+
glyph_renderers = []
162+
start = 0
163+
for group in HB_handles:
164+
self.logger.debug("%d elements", len(group))
165+
glyph_renderers.append(glyph_renderers_flattened[start: start+len(group)])
166+
start += len(group)
167+
self.logger.debug("%d, %d, %d", len(HB_handles),
168+
len(glyph_renderers), num_total_lines)
169+
HB_handles_flattened = [a for b in HB_handles for a in b]
170+
args_checkbox = {name: glyph for name, glyph in zip(glyph_renderers_flattened,
171+
HB_handles_flattened)}
172+
code_checkbox = "len_labels = " + str(len(HB_handles)) + "; glyph_renderers = [" + ','.join(['[' + ','.join([str(idx) for idx
173+
in group]) + ']' for
174+
group in glyph_renderers]) + '];' + """
175+
for (i = 0; i < len_labels; i++) {
176+
if (cb_obj.active.includes(i)) {
177+
// console.log('Setting to true: ' + i + '(' + glyph_renderers[i].length + ')')
178+
for (j = 0; j < glyph_renderers[i].length; j++) {
179+
glyph_renderers[i][j].visible = true;
180+
// console.log('Setting to true: ' + i + ' : ' + j)
181+
}
182+
} else {
183+
// console.log('Setting to false: ' + i + '(' + glyph_renderers[i].length + ')')
184+
for (j = 0; j < glyph_renderers[i].length; j++) {
185+
glyph_renderers[i][j].visible = false;
186+
// console.log('Setting to false: ' + i + ' : ' + j)
187+
}
188+
}
189+
}
190+
"""
191+
iteration_labels = ['warmstart data' if l == -1 or l == '-1' else str(l) for l in HB_iterations]
192+
self.logger.debug("iteration_labels: %s", str(iteration_labels))
193+
self.logger.debug("HB_iterations: %s", str(HB_iterations))
194+
195+
callback = CustomJS(args=args_checkbox, code=code_checkbox)
196+
# TODO Use the CheckboxButtonGroup code after upgrading bokeh to >0.12.14 (it's prettier)
197+
#checkbox = CheckboxButtonGroup(
198+
checkbox = CheckboxGroup(
199+
labels=iteration_labels,
200+
active=list(range(len(iteration_labels))),
201+
callback=callback)
202+
# Select all/none:
203+
handle_list_as_string = str(list(range(len(HB_handles))))
204+
select_all = Button(label="All", callback=CustomJS(args=dict({'checkbox':checkbox}, **args_checkbox),
205+
code="var labels = " + handle_list_as_string + "; checkbox.active = labels;" + code_checkbox.replace('cb_obj', 'checkbox')))
206+
select_none = Button(label="None", callback=CustomJS(args=dict({'checkbox':checkbox}, **args_checkbox),
207+
code="var labels = []; checkbox.active = labels;" + code_checkbox.replace('cb_obj', 'checkbox')))
208+
# Put it all together
209+
layout = column(p, row(widgetbox(select_all, select_none), widgetbox(checkbox)))
210+
return layout
211+
212+
def get_longest_run(self, c_id, result_object):
213+
all_runs = result_object.get_runs_by_id(c_id)
214+
longest_run = all_runs[-1]
215+
while longest_run.loss is None:
216+
all_runs.pop()
217+
if len(all_runs) == 0:
218+
return False
219+
longest_run = all_runs[-1]
220+
return longest_run
221+
222+
223+
def get_jupyter(self):
224+
output_notebook()
225+
show(self.bokeh_plot)
226+
227+
def get_html(self, d=None):
228+
bokeh_components = components(self.bokeh_plot)
229+
if d is not None:
230+
d["BOHB Learning Curves"] = {"bokeh" : bokeh_components}
231+
return bokeh_components
232+

cave/analyzer/box_violin.py

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
import os
2+
import logging
3+
from collections import OrderedDict
4+
5+
from pandas import DataFrame
6+
import numpy as np
7+
8+
from cave.feature_analysis.feature_analysis import FeatureAnalysis
9+
from cave.analyzer.base_analyzer import BaseAnalyzer
10+
from cave.html.html_helpers import figure_to_html
11+
12+
class BoxViolin(BaseAnalyzer):
13+
def __init__(self, output_dir, scenario, feat_names, feat_importance):
14+
self.logger = logging.getLogger(self.__module__ + '.' + self.__class__.__name__)
15+
16+
self.output_dir = output_dir
17+
18+
feat_analysis = FeatureAnalysis(output_dn=output_dir,
19+
scenario=scenario,
20+
feat_names=feat_names,
21+
feat_importance=feat_importance)
22+
self.name_plots = feat_analysis.get_box_violin_plots()
23+
24+
def get_plots(self):
25+
return [x[1] for x in self.name_plots]
26+
27+
def get_html(self, d=None):
28+
if d is not None:
29+
d["Violin and Box Plots"] = OrderedDict()
30+
for plot_tuple in self.name_plots:
31+
key = "%s" % (plot_tuple[0])
32+
d["Violin and Box Plots"][key] = {"figure": plot_tuple[1]}
33+
return figure_to_html(self.get_plots(), max_in_a_row=3, true_break_between_rows=True)
34+
35+
def get_jupyter(self):
36+
from IPython.core.display import HTML, display
37+
display(HTML(self.get_html()))

0 commit comments

Comments
 (0)