mirror of
https://github.com/wassname/optuna-dashboard.git
synced 2026-09-06 17:00:30 +08:00
135 lines
4.5 KiB
Python
135 lines
4.5 KiB
Python
from __future__ import annotations
|
|
|
|
import math
|
|
from typing import TYPE_CHECKING
|
|
import uuid
|
|
|
|
from optuna import Study
|
|
|
|
|
|
if TYPE_CHECKING:
|
|
from typing import Any
|
|
|
|
from optuna.storages import BaseStorage
|
|
import plotly.graph_objs as go
|
|
|
|
|
|
SYSTEM_ATTR_PLOT_DATA = "dashboard:plot_data:"
|
|
SYSTEM_ATTR_MAX_LENGTH = 2045
|
|
|
|
|
|
def save_plotly_graph_object(
|
|
study: Study, figure: go.Figure, *, graph_object_id: str | None = None
|
|
) -> str:
|
|
"""Save the user-defined plotly's graph object to the study.
|
|
|
|
Example:
|
|
|
|
.. code-block:: python
|
|
|
|
import optuna
|
|
from optuna_dashboard import save_plotly_graph_object
|
|
|
|
def objective(trial):
|
|
x = trial.suggest_float("x", -100, 100)
|
|
y = trial.suggest_categorical("y", [-1, 0, 1])
|
|
return x**2 + y
|
|
|
|
study = optuna.create_study()
|
|
study.optimize(objective, n_trials=100)
|
|
|
|
figure = optuna.visualization.plot_optimization_history(study)
|
|
save_plotly_graph_object(study, figure)
|
|
|
|
Args:
|
|
study:
|
|
Target study object.
|
|
plot_data:
|
|
The plotly's graph object to save.
|
|
graph_object_id:
|
|
Unique identifier of the graph object. If specified, the graph object is overwritten.
|
|
This must be a valid HTML id attribute value.
|
|
|
|
Returns:
|
|
The graph object ID.
|
|
"""
|
|
if graph_object_id is not None and not is_valid_graph_object_id(graph_object_id):
|
|
raise ValueError("graph_object_id must be a valid HTML id attribute value.")
|
|
|
|
storage = study._storage
|
|
study_id = study._study_id
|
|
|
|
graph_object_id = graph_object_id or str(uuid.uuid4())
|
|
key = SYSTEM_ATTR_PLOT_DATA + graph_object_id + ":"
|
|
plot_data_json_str = figure.to_json()
|
|
save_graph_object_json(storage, study_id, key, plot_data_json_str)
|
|
return graph_object_id
|
|
|
|
|
|
def save_graph_object_json(
|
|
storage: BaseStorage, study_id: int, key_prefix: str, plot_data_json_str: str
|
|
) -> None:
|
|
plot_data_system_attrs = split_plot_data(plot_data_json_str, key_prefix)
|
|
for k, v in plot_data_system_attrs.items():
|
|
storage.set_study_system_attr(study_id, k, v)
|
|
|
|
# Clear previous graph object attributes
|
|
study_system_attrs = storage.get_study_system_attrs(study_id)
|
|
all_plot_data_system_attrs = [k for k in study_system_attrs if k.startswith(key_prefix)]
|
|
if len(all_plot_data_system_attrs) > len(plot_data_system_attrs):
|
|
for i in range(len(plot_data_system_attrs), len(all_plot_data_system_attrs)):
|
|
storage.set_study_system_attr(study_id, f"{key_prefix}{i}", "")
|
|
|
|
|
|
def list_graph_object_ids(system_attrs: dict[str, Any]) -> list[str]:
|
|
titles = set()
|
|
for key in system_attrs:
|
|
if not key.startswith(SYSTEM_ATTR_PLOT_DATA):
|
|
continue
|
|
|
|
s = key.split(":", maxsplit=2) # e.g. ["dashboard", "plot_data", "Optimization History:1"]
|
|
if len(s) != 3:
|
|
continue
|
|
# Please note that title may contain ":".
|
|
title = s[2].rsplit(":", maxsplit=1)[0]
|
|
titles.add(title)
|
|
return list(titles)
|
|
|
|
|
|
def get_plotly_graph_objects(system_attrs: dict[str, Any]) -> dict[str, str]:
|
|
graph_objects = {}
|
|
for title in list_graph_object_ids(system_attrs):
|
|
key_prefix = SYSTEM_ATTR_PLOT_DATA + title + ":"
|
|
plot_data_attrs = {k: v for k, v in system_attrs.items() if k.startswith(key_prefix)}
|
|
graph_objects[title] = concat_plot_data(plot_data_attrs, key_prefix)
|
|
return graph_objects
|
|
|
|
|
|
def split_plot_data(plot_data_str: str, key_prefix: str) -> dict[str, str]:
|
|
plot_data_len = len(plot_data_str)
|
|
attrs = {}
|
|
for i in range(math.ceil(plot_data_len / SYSTEM_ATTR_MAX_LENGTH)):
|
|
start = i * SYSTEM_ATTR_MAX_LENGTH
|
|
end = min((i + 1) * SYSTEM_ATTR_MAX_LENGTH, plot_data_len)
|
|
attrs[f"{key_prefix}{i}"] = plot_data_str[start:end]
|
|
return attrs
|
|
|
|
|
|
def concat_plot_data(plot_data_attrs: dict[str, str], key_prefix: str) -> str:
|
|
return "".join(plot_data_attrs[f"{key_prefix}{i}"] for i in range(len(plot_data_attrs)))
|
|
|
|
|
|
def is_valid_graph_object_id(graph_object_id: str) -> bool:
|
|
if len(graph_object_id) == 0:
|
|
return False
|
|
|
|
# Can only contain letters [A-Za-z], numbers [0-9], hyphens ("-"), underscores ("_"),
|
|
# colons, and periods.
|
|
if not all(
|
|
"a" <= c <= "z" or "A" <= c <= "Z" or "0" <= c <= "9" or c in ("-", "_", ":", ".")
|
|
for c in graph_object_id[1:]
|
|
):
|
|
return False
|
|
# Unlike HTML id attribute, graph object id can begin with a letter [A-Za-z]
|
|
return True
|