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 {