From 192b24eeb36edaa9e0a5bc4f8295176d9d6cb91e Mon Sep 17 00:00:00 2001 From: c-bata Date: Wed, 4 Jan 2023 19:38:44 +0900 Subject: [PATCH] Add API to set objective names --- README.md | 8 +++-- optuna_dashboard/__init__.py | 1 + optuna_dashboard/_named_objectives.py | 35 +++++++++++++++++++ optuna_dashboard/_serializer.py | 4 +++ optuna_dashboard/ts/apiClient.ts | 2 ++ .../ts/components/GraphContour.tsx | 3 +- optuna_dashboard/ts/components/GraphEdf.tsx | 3 +- .../ts/components/GraphHistory.tsx | 3 +- .../GraphHyperparameterImportances.tsx | 7 +++- .../ts/components/GraphParallelCoordinate.tsx | 3 +- .../ts/components/GraphParetoFront.tsx | 5 +-- optuna_dashboard/ts/components/GraphSlice.tsx | 5 +-- optuna_dashboard/ts/components/TrialTable.tsx | 3 +- optuna_dashboard/ts/types/index.d.ts | 1 + 14 files changed, 71 insertions(+), 12 deletions(-) create mode 100644 optuna_dashboard/_named_objectives.py diff --git a/README.md b/README.md index 0853df42..9e34b760 100644 --- a/README.md +++ b/README.md @@ -88,14 +88,18 @@ This function uses wsgiref module which is not intended for the production use. This function exposes WSGI interface for people who want to run on the production-class WSGI servers like Gunicorn or uWSGI. -**`save_study_note(study: Study, body: string) -> None`** +**`save_study_note(study: Study, body: str) -> None`** Save the note (Markdown format) to the Study. -**`save_trial_note(trial: Trial, body: string) -> None`** +**`save_trial_note(trial: Trial, body: str) -> None`** Save the note (Markdown format) to the Trial. +**`set_objective_names(study: Study, names: list[str]) -> None`** + +Set the names of objectives. + ## Using an official Docker image diff --git a/optuna_dashboard/__init__.py b/optuna_dashboard/__init__.py index 9c23bd5e..f59d98a5 100644 --- a/optuna_dashboard/__init__.py +++ b/optuna_dashboard/__init__.py @@ -1,5 +1,6 @@ from ._app import run_server # noqa from ._app import wsgi # noqa +from ._named_objectives import set_objective_names # noqa from ._note import save_study_note # noqa from ._note import save_trial_note # noqa diff --git a/optuna_dashboard/_named_objectives.py b/optuna_dashboard/_named_objectives.py new file mode 100644 index 00000000..e61dcc82 --- /dev/null +++ b/optuna_dashboard/_named_objectives.py @@ -0,0 +1,35 @@ +from __future__ import annotations + +from typing import Any +from typing import Optional + +import optuna + + +SYSTEM_ATTR_NAME = "dashboard:objective_names" + + +def set_objective_names(study: optuna.Study, names: list[str]) -> None: + """Set the names of objectives. + + Example: + + .. code-block:: python + + import optuna + from optuna_dashboard import set_objective_names + + study = optuna.create_study(directions=["minimize", "minimize"]) + set_objective_names(study, ["val_loss", "flops"]) + """ + storage = study._storage + study_id = study._study_id + + directions = storage.get_study_directions(study_id) + if len(directions) != len(names): + raise ValueError("names must be the same length with the number of objectives.") + storage.set_study_system_attr(study_id, SYSTEM_ATTR_NAME, names) + + +def get_objective_names(system_attrs: dict[str, Any]) -> Optional[list[str]]: + return system_attrs.get(SYSTEM_ATTR_NAME) diff --git a/optuna_dashboard/_serializer.py b/optuna_dashboard/_serializer.py index 764083b3..6442fa91 100644 --- a/optuna_dashboard/_serializer.py +++ b/optuna_dashboard/_serializer.py @@ -11,6 +11,7 @@ from optuna.study import StudySummary from optuna.trial import FrozenTrial from . import _note as note +from ._named_objectives import get_objective_names if TYPE_CHECKING: @@ -99,6 +100,9 @@ def serialize_study_detail( serialized["union_user_attrs"] = [{"key": a[0], "sortable": a[1]} for a in union_user_attrs] serialized["has_intermediate_values"] = has_intermediate_values serialized["note"] = note.get_note_from_system_attrs(system_attrs, None) + objective_names = get_objective_names(system_attrs) + if objective_names: + serialized["objective_names"] = objective_names return serialized diff --git a/optuna_dashboard/ts/apiClient.ts b/optuna_dashboard/ts/apiClient.ts index e1718b9c..d617a91a 100644 --- a/optuna_dashboard/ts/apiClient.ts +++ b/optuna_dashboard/ts/apiClient.ts @@ -49,6 +49,7 @@ interface StudyDetailResponse { union_user_attrs: AttributeSpec[] has_intermediate_values: boolean note: Note + objective_names?: string[] } export const getStudyDetailAPI = ( @@ -80,6 +81,7 @@ export const getStudyDetailAPI = ( union_user_attrs: res.data.union_user_attrs, has_intermediate_values: res.data.has_intermediate_values, note: res.data.note, + objective_names: res.data.objective_names, } }) } diff --git a/optuna_dashboard/ts/components/GraphContour.tsx b/optuna_dashboard/ts/components/GraphContour.tsx index a26c3e16..b6c2ba20 100644 --- a/optuna_dashboard/ts/components/GraphContour.tsx +++ b/optuna_dashboard/ts/components/GraphContour.tsx @@ -41,6 +41,7 @@ export const Contour: FC<{ const [xParam, setXParam] = useState("") const [yParam, setYParam] = useState("") const paramNames = study?.union_search_space.map((s) => s.name) + const objectiveNames: string[] = study?.objective_names || [] if (!xParam && paramNames && paramNames.length > 0) { setXParam(paramNames[0]) @@ -85,7 +86,7 @@ export const Contour: FC<{ diff --git a/optuna_dashboard/ts/components/GraphEdf.tsx b/optuna_dashboard/ts/components/GraphEdf.tsx index c266de67..94e052ec 100644 --- a/optuna_dashboard/ts/components/GraphEdf.tsx +++ b/optuna_dashboard/ts/components/GraphEdf.tsx @@ -20,6 +20,7 @@ export const Edf: FC<{ }> = ({ study = null }) => { const theme = useTheme() const [objectiveId, setObjectiveId] = useState(0) + const objectiveNames: string[] = study?.objective_names || [] const handleObjectiveChange = (event: SelectChangeEvent) => { setObjectiveId(event.target.value as number) @@ -48,7 +49,7 @@ export const Edf: FC<{ diff --git a/optuna_dashboard/ts/components/GraphHistory.tsx b/optuna_dashboard/ts/components/GraphHistory.tsx index 03907c12..9826dfef 100644 --- a/optuna_dashboard/ts/components/GraphHistory.tsx +++ b/optuna_dashboard/ts/components/GraphHistory.tsx @@ -28,6 +28,7 @@ export const GraphHistory: FC<{ const [logScale, setLogScale] = useState(false) const [filterCompleteTrial, setFilterCompleteTrial] = useState(false) const [filterPrunedTrial, setFilterPrunedTrial] = useState(false) + const objectiveNames: string[] = study?.objective_names || [] const handleObjectiveChange = (event: SelectChangeEvent) => { setObjectiveId(event.target.value as number) @@ -92,7 +93,7 @@ export const GraphHistory: FC<{ diff --git a/optuna_dashboard/ts/components/GraphHyperparameterImportances.tsx b/optuna_dashboard/ts/components/GraphHyperparameterImportances.tsx index faf090fe..c258d698 100644 --- a/optuna_dashboard/ts/components/GraphHyperparameterImportances.tsx +++ b/optuna_dashboard/ts/components/GraphHyperparameterImportances.tsx @@ -32,6 +32,7 @@ export const GraphHyperparameterImportanceBeta: FC<{ const numCompletedTrials = study?.trials.filter((t) => t.state === "Complete").length || 0 const nObjectives = useStudyDirections(studyId)?.length + const objectiveNames: string[] = study?.objective_names || [] useEffect(() => { action.updateParamImportance(studyId) @@ -48,7 +49,11 @@ export const GraphHyperparameterImportanceBeta: FC<{ {Array.from({ length: nObjectives || 1 }, (_, i) => { let title = `Importance for the Objective Value` if (nObjectives != null && nObjectives > 1) { - title = `Importance for the Objective ${i}` + if (objectiveNames.length == nObjectives) { + title = `Importance for ${objectiveNames[i]} (Objective ${i})` + } else { + title = `Importance for the Objective ${i}` + } } return ( diff --git a/optuna_dashboard/ts/components/GraphParallelCoordinate.tsx b/optuna_dashboard/ts/components/GraphParallelCoordinate.tsx index 4331a6bd..0c74c130 100644 --- a/optuna_dashboard/ts/components/GraphParallelCoordinate.tsx +++ b/optuna_dashboard/ts/components/GraphParallelCoordinate.tsx @@ -20,6 +20,7 @@ export const GraphParallelCoordinate: FC<{ }> = ({ study = null }) => { const theme = useTheme() const [objectiveId, setObjectiveId] = useState(0) + const objectiveNames: string[] = study?.objective_names || [] const handleObjectiveChange = (event: SelectChangeEvent) => { setObjectiveId(event.target.value as number) @@ -49,7 +50,7 @@ export const GraphParallelCoordinate: FC<{ diff --git a/optuna_dashboard/ts/components/GraphParetoFront.tsx b/optuna_dashboard/ts/components/GraphParetoFront.tsx index c1f737ef..65933aaf 100644 --- a/optuna_dashboard/ts/components/GraphParetoFront.tsx +++ b/optuna_dashboard/ts/components/GraphParetoFront.tsx @@ -21,6 +21,7 @@ export const GraphParetoFront: FC<{ const theme = useTheme() const [objectiveXId, setObjectiveXId] = useState(0) const [objectiveYId, setObjectiveYId] = useState(1) + const objectiveNames: string[] = study?.objective_names || [] const handleObjectiveXChange = (event: SelectChangeEvent) => { setObjectiveXId(event.target.value as number) @@ -55,7 +56,7 @@ export const GraphParetoFront: FC<{ @@ -65,7 +66,7 @@ export const GraphParetoFront: FC<{ diff --git a/optuna_dashboard/ts/components/GraphSlice.tsx b/optuna_dashboard/ts/components/GraphSlice.tsx index 2a101237..2ce96dcb 100644 --- a/optuna_dashboard/ts/components/GraphSlice.tsx +++ b/optuna_dashboard/ts/components/GraphSlice.tsx @@ -32,6 +32,7 @@ export const GraphSlice: FC<{ const distributions = new Map( study?.union_search_space.map((s) => [s.name, s.distribution]) ) + const objectiveNames: string[] = study?.objective_names || [] if (selected === null && paramNames && paramNames.length > 0) { const distribution = distributions.get(paramNames[0]) || "" setSelected(paramNames[0]) @@ -82,7 +83,7 @@ export const GraphSlice: FC<{ @@ -93,7 +94,7 @@ export const GraphSlice: FC<{ diff --git a/optuna_dashboard/ts/components/TrialTable.tsx b/optuna_dashboard/ts/components/TrialTable.tsx index e676a1b2..7d43c088 100644 --- a/optuna_dashboard/ts/components/TrialTable.tsx +++ b/optuna_dashboard/ts/components/TrialTable.tsx @@ -8,6 +8,7 @@ export const TrialTable: FC<{ initialRowsPerPage?: number }> = ({ studyDetail, initialRowsPerPage }) => { const trials: Trial[] = studyDetail !== null ? studyDetail.trials : [] + const objectiveNames: string[] = studyDetail?.objective_names || [] const columns: DataGridColumn[] = [ { field: "number", label: "Number", sortable: true, padding: "none" }, @@ -55,7 +56,7 @@ export const TrialTable: FC<{ const objectiveColumns: DataGridColumn[] = studyDetail.directions.map((s, objectiveId) => ({ field: "values", - label: `Objective ${objectiveId}`, + label: objectiveNames.length === studyDetail?.directions.length ? objectiveNames[objectiveId] : `Objective ${objectiveId}`, sortable: true, less: (firstEl, secondEl): number => { const firstVal = firstEl.values?.[objectiveId] diff --git a/optuna_dashboard/ts/types/index.d.ts b/optuna_dashboard/ts/types/index.d.ts index 83404c37..3e70d286 100644 --- a/optuna_dashboard/ts/types/index.d.ts +++ b/optuna_dashboard/ts/types/index.d.ts @@ -106,6 +106,7 @@ declare interface StudyDetail { union_user_attrs: AttributeSpec[] has_intermediate_values: boolean note: Note + objective_names?: string[] } declare interface StudyDetails {