Skip to content

Commit

Permalink
Merge pull request #101 from automl/feature/symbolic-explanations
Browse files Browse the repository at this point in the history
Feature/symbolic explanations
  • Loading branch information
sarah-segel authored Feb 21, 2024
2 parents 0cda1d4 + d4101f6 commit e6c1f2e
Show file tree
Hide file tree
Showing 20 changed files with 877 additions and 34 deletions.
9 changes: 8 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,9 +1,16 @@
# Version 1.1.4
# Version 1.2

## Plugins
- Add symbolic explanations plugin (#46).

## Enhancements
- Fix lower bounds of dependency versions.
- Allow to load multi-objective SMAC3v2 and add example (#69)
- Do not disable existing loggers.
- Update author email.
- Add exit button which first deletes running jobs and then terminates DeepCave.
- Nicer handling of Keyboard Interrupt.
- Disable debug mode.

## Bug-Fixes
- Don't convert BOHB runs with status 'running' (consistent with SMAC).
Expand Down
6 changes: 3 additions & 3 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
# are usually completed in github actions.

SHELL := /bin/bash
VERSION := 1.1.3
VERSION := 1.2

NAME := DeepCAVE
PACKAGE_NAME := deepcave
Expand Down Expand Up @@ -42,7 +42,7 @@ MYPY ?= mypy
PRECOMMIT ?= pre-commit
FLAKE8 ?= flake8

install:
install:
$(PIP) install -e .

install-dev:
Expand Down Expand Up @@ -115,7 +115,7 @@ build:
# This is done to prevent accidental publishing but provide the same conveniences
publish: clean build
read -p "Did you update the version number in Makefile and deepcave/__init__.py?"

$(PIP) install twine
$(PYTHON) -m twine upload --repository testpypi ${DIST}/*
@echo
Expand Down
4 changes: 2 additions & 2 deletions deepcave/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,15 +9,15 @@
name = "DeepCAVE"
package_name = "deepcave"
author = "R. Sass and E. Bergman and A. Biedenkapp and F. Hutter and M. Lindauer"
author_email = "sass@tnt.uni-hannover.de"
author_email = "s.segel@ai.uni-hannover.de"
description = "An interactive framework to visualize and analyze your AutoML process in real-time."
url = "automl.org"
project_urls = {
"Documentation": "https://automl.github.io/DeepCAVE/main",
"Source Code": "https://github.com/automl/deepcave",
}
copyright = f"Copyright {datetime.date.today().strftime('%Y')}, {author}"
version = "1.1.3"
version = "1.2"

_exec_file = sys.argv[0]
_exec_files = ["server.py", "worker.py", "sphinx-build"]
Expand Down
7 changes: 4 additions & 3 deletions deepcave/cli.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,3 @@
from typing import Any, List

import multiprocessing
import subprocess
from pathlib import Path
Expand Down Expand Up @@ -49,4 +47,7 @@ def execute(_) -> None:


def main() -> None:
app.run(execute)
try:
app.run(execute)
except KeyboardInterrupt:
exit("KeyboardInterrupt.")
6 changes: 5 additions & 1 deletion deepcave/config.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
class Config:
# General config
TITLE: str = "DeepCAVE"
DEBUG: bool = True
DEBUG: bool = False
# How often to refresh background activities (such as update the sidebar or process button for
# static plugins). Value in milliseconds.
REFRESH_RATE: int = 500
Expand Down Expand Up @@ -49,6 +49,9 @@ def PLUGINS(self) -> Dict[str, List["Plugin"]]:
from deepcave.plugins.budget.budget_correlation import BudgetCorrelation
from deepcave.plugins.hyperparameter.importances import Importances
from deepcave.plugins.hyperparameter.pdp import PartialDependencies
from deepcave.plugins.hyperparameter.symbolic_explanations import (
SymbolicExplanations,
)
from deepcave.plugins.objective.configuration_cube import ConfigurationCube
from deepcave.plugins.objective.cost_over_time import CostOverTime
from deepcave.plugins.objective.parallel_coordinates import ParallelCoordinates
Expand All @@ -75,6 +78,7 @@ def PLUGINS(self) -> Dict[str, List["Plugin"]]:
"Hyperparameter Analysis": [
Importances(),
PartialDependencies(),
SymbolicExplanations(),
],
}
return plugins
Expand Down
2 changes: 1 addition & 1 deletion deepcave/evaluators/epm/random_forest.py
Original file line number Diff line number Diff line change
Expand Up @@ -180,7 +180,7 @@ def _impute_inactive(self, X: np.ndarray) -> np.ndarray:

def _check_dimensions(self, X: np.ndarray, Y: Optional[np.ndarray] = None) -> None:
"""
Checks if the dimensions of X and Y are correct wrt features.
Checks if the dimensions of X and Y are correct with respect to features.
Parameters
----------
Expand Down
2 changes: 1 addition & 1 deletion deepcave/evaluators/fanova.py
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ def calculate(
seed: int = 0,
) -> None:
"""
Get the data wrt budget and trains the forest on the encoded data.
Get the data with respect to budget and trains the forest on the encoded data.
Note
----
Expand Down
42 changes: 40 additions & 2 deletions deepcave/layouts/header.py
Original file line number Diff line number Diff line change
@@ -1,15 +1,22 @@
import os
import time

import dash_bootstrap_components as dbc
from dash import dcc, html
from dash.dependencies import Input, Output

from deepcave import app, c
from deepcave import app, c, queue
from deepcave.layouts import Layout


class HeaderLayout(Layout):
def register_callbacks(self) -> None:
super().register_callbacks()
self._callback_update_matplotlib_mode()
self._callback_delete_jobs()
self._callback_terminate_deepcave()

def _callback_update_matplotlib_mode(self) -> None:
outputs = [
Output("matplotlib-mode-toggle", "color"),
Output("matplotlib-mode-badge", "children"),
Expand All @@ -21,7 +28,7 @@ def register_callbacks(self) -> None:
]

@app.callback(outputs, inputs)
def update_matplotlib_mode(n_clicks, pathname):
def callback(n_clicks, pathname):
update = None
mode = c.get("matplotlib-mode")
if mode is None:
Expand All @@ -37,6 +44,34 @@ def update_matplotlib_mode(n_clicks, pathname):
else:
return "secondary", "off", update

def _callback_delete_jobs(self) -> None:
inputs = [Input("exit-deepcave", "n_clicks")]
outputs = [
Output("exit-deepcave", "color"),
Output("exit-deepcave", "children"),
Output("exit-deepcave", "disabled"),
]

@app.callback(inputs, outputs)
def callback(n_clicks):
# When clicking the Exit button, we first want to delete existing jobs and update the button
if n_clicks is not None:
queue.delete_jobs()
return "danger", "Terminated DeepCAVE", True
else:
return "primary", "Exit", False

def _callback_terminate_deepcave(self) -> None:
inputs = [Input("exit-deepcave", "n_clicks")]
outputs = []

@app.callback(inputs, outputs)
def callback(n_clicks):
# Then we want to terminate DeepCAVE
if n_clicks is not None:
time.sleep(1)
os._exit(130)

def __call__(self) -> html.Header:
return html.Header(
className="navbar navbar-dark sticky-top bg-dark flex-md-nowrap p-0 shadow",
Expand All @@ -59,5 +94,8 @@ def __call__(self) -> html.Header:
className="me-2",
id="matplotlib-mode-toggle",
),
dbc.Button(
"Exit", color="secondary", className="me-2", id="exit-deepcave", disabled=False
),
],
)
21 changes: 13 additions & 8 deletions deepcave/plugins/hyperparameter/pdp.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@
class PartialDependencies(StaticPlugin):
id = "pdp"
name = "Partial Dependencies"
icon = "far fa-grip-lines"
icon = "fas fa-grip-lines"
help = "docs/plugins/partial_dependencies.rst"
activate_run_selection = True

Expand Down Expand Up @@ -149,9 +149,7 @@ def load_dependency_inputs(self, run, previous_inputs, inputs):

if objective_value is None:
objective_value = objective_ids[0]
if budget_value is None:
budget_value = budget_ids[-1]
if hp1_value is None:
hp1_value = hp_names[0]

return {
Expand All @@ -163,7 +161,6 @@ def load_dependency_inputs(self, run, previous_inputs, inputs):
},
"hyperparameter_name_2": {
"options": get_checklist_options([None] + hp_names),
"value": hp2_value,
},
}

Expand Down Expand Up @@ -237,7 +234,7 @@ def get_output_layout(register):
return dcc.Graph(register("graph", "figure"), style={"height": Config.FIGURE_HEIGHT})

@staticmethod
def load_outputs(run, inputs, outputs):
def get_pdp_figure(run, inputs, outputs, show_confidence, show_ice, title=None):
# Parse inputs
hp1_name = inputs["hyperparameter_name_1"]
hp1_idx = run.configspace.get_idx_by_hyperparameter_name(hp1_name)
Expand All @@ -250,9 +247,6 @@ def load_outputs(run, inputs, outputs):
hp2_idx = run.configspace.get_idx_by_hyperparameter_name(hp2_name)
hp2 = run.configspace.get_hyperparameter(hp2_name)

show_confidence = inputs["show_confidence"]
show_ice = inputs["show_ice"]

objective = run.get_objective(inputs["objective_id"])
objective_name = objective.name

Expand Down Expand Up @@ -323,6 +317,7 @@ def load_outputs(run, inputs, outputs):
"yaxis": {
"title": objective_name,
},
"title": title
}
)
else:
Expand All @@ -349,10 +344,20 @@ def load_outputs(run, inputs, outputs):
xaxis=dict(tickvals=x_tickvals, ticktext=x_ticktext, title=hp1_name),
yaxis=dict(tickvals=y_tickvals, ticktext=y_ticktext, title=hp2_name),
margin=Config.FIGURE_MARGIN,
title=title
)
)

figure = go.Figure(data=traces, layout=layout)
save_image(figure, "pdp.pdf")

return figure

@staticmethod
def load_outputs(run, inputs, outputs):
show_confidence = inputs["show_confidence"]
show_ice = inputs["show_ice"]

figure = PartialDependencies.get_pdp_figure(run, inputs, outputs, show_confidence, show_ice)

return figure
Loading

0 comments on commit e6c1f2e

Please sign in to comment.