mirror of
https://github.com/wassname/optuna-dashboard.git
synced 2026-09-10 12:23:22 +08:00
Merge branch 'main' into feat/jupyter-lab-optuna
This commit is contained in:
@@ -109,9 +109,27 @@ jobs:
|
||||
with:
|
||||
node-version: '20'
|
||||
|
||||
- name: Build rustlib for standalone_app
|
||||
working-directory: rustlib
|
||||
run: |
|
||||
curl https://rustwasm.github.io/wasm-pack/installer/init.sh -sSf | sh
|
||||
wasm-pack build --target web
|
||||
|
||||
- name: Setup tslib
|
||||
run: make tslib
|
||||
|
||||
- name: Build standalone_app for vscode extension
|
||||
working-directory: standalone_app
|
||||
run: |
|
||||
npm install
|
||||
npm run build:vscode
|
||||
|
||||
- name: Build standalone_app for GitHub pages
|
||||
working-directory: standalone_app
|
||||
run: |
|
||||
npm install
|
||||
npx vite build --out-dir ./gh-pages
|
||||
|
||||
- name: Build bundle.js
|
||||
working-directory: optuna_dashboard
|
||||
run: |
|
||||
|
||||
@@ -74,7 +74,7 @@ def get_artifact_path(
|
||||
|
||||
|
||||
def register_artifact_route(
|
||||
app: Bottle, storage: BaseStorage, artifact_store: Optional[ArtifactStore]
|
||||
app: Bottle, storage: BaseStorage, artifact_store: ArtifactStore | None
|
||||
) -> None:
|
||||
@app.get("/artifacts/<study_id:int>/<artifact_id:re:[0-9a-fA-F-]+>")
|
||||
def proxy_study_artifact(study_id: int, artifact_id: str) -> HTTPResponse | bytes:
|
||||
@@ -243,8 +243,8 @@ def upload_artifact(
|
||||
trial: optuna.Trial,
|
||||
file_path: str,
|
||||
*,
|
||||
mimetype: Optional[str] = None,
|
||||
encoding: Optional[str] = None,
|
||||
mimetype: str | None = None,
|
||||
encoding: str | None = None,
|
||||
) -> str:
|
||||
"""Upload an artifact (files), which is associated with the trial.
|
||||
|
||||
@@ -300,7 +300,7 @@ def _dashboard_artifact_prefix(trial_id: int) -> str:
|
||||
|
||||
def get_study_artifact_meta(
|
||||
storage: BaseStorage, study_id: int, artifact_id: str
|
||||
) -> Optional[ArtifactMeta]:
|
||||
) -> ArtifactMeta | None:
|
||||
study_system_attrs = storage.get_study_system_attrs(study_id)
|
||||
attr_key = ARTIFACTS_ATTR_PREFIX + artifact_id
|
||||
artifact_meta = study_system_attrs.get(attr_key)
|
||||
@@ -311,7 +311,7 @@ def get_study_artifact_meta(
|
||||
|
||||
def get_trial_artifact_meta(
|
||||
storage: BaseStorage, study_id: int, trial_id: int, artifact_id: str
|
||||
) -> Optional[ArtifactMeta]:
|
||||
) -> ArtifactMeta | None:
|
||||
# Search study_system_attrs due to backward compatibility.
|
||||
study_system_attrs = storage.get_study_system_attrs(study_id)
|
||||
attr_key = _dashboard_artifact_prefix(trial_id=trial_id) + artifact_id
|
||||
|
||||
@@ -12,7 +12,6 @@ from optuna_dashboard.artifact.exceptions import ArtifactNotFound
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from typing import BinaryIO
|
||||
from typing import Optional
|
||||
|
||||
from mypy_boto3_s3 import S3Client
|
||||
|
||||
@@ -43,7 +42,7 @@ class Boto3Backend:
|
||||
"""
|
||||
|
||||
def __init__(
|
||||
self, bucket_name: str, client: Optional[S3Client] = None, *, avoid_buf_copy: bool = False
|
||||
self, bucket_name: str, client: S3Client | None = None, *, avoid_buf_copy: bool = False
|
||||
) -> None:
|
||||
self.bucket = bucket_name
|
||||
self.client = client or boto3.client("s3")
|
||||
|
||||
@@ -1,3 +1,6 @@
|
||||
from __future__ import annotations
|
||||
|
||||
|
||||
class ArtifactNotFound(Exception):
|
||||
"""Exception raised when an artifact is not found.
|
||||
|
||||
|
||||
Generated
+4
-25
@@ -73,14 +73,13 @@
|
||||
"@mui/lab": "^5.0.0-alpha.170",
|
||||
"@mui/material": "^5.15.10",
|
||||
"@mui/system": "^5.15.9",
|
||||
"@optuna/storage": "file:../storage",
|
||||
"@tanstack/react-table": "^8.17.3",
|
||||
"plotly.js-dist-min": "^2.30.1",
|
||||
"react": "^18.2.0",
|
||||
"react-dom": "^18.2.0",
|
||||
"recoil": "^0.7.7"
|
||||
"react-dom": "^18.2.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@optuna/storage": "file:../storage",
|
||||
"@optuna/types": "file:../types",
|
||||
"@storybook/addon-essentials": "^8.0.4",
|
||||
"@storybook/addon-interactions": "^8.0.4",
|
||||
@@ -7186,10 +7185,6 @@
|
||||
"gunzip-maybe": "bin.js"
|
||||
}
|
||||
},
|
||||
"../tslib/react/node_modules/hamt_plus": {
|
||||
"version": "1.0.2",
|
||||
"license": "MIT"
|
||||
},
|
||||
"../tslib/react/node_modules/handlebars": {
|
||||
"version": "4.7.8",
|
||||
"dev": true,
|
||||
@@ -9944,24 +9939,6 @@
|
||||
"dev": true,
|
||||
"license": "0BSD"
|
||||
},
|
||||
"../tslib/react/node_modules/recoil": {
|
||||
"version": "0.7.7",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"hamt_plus": "1.0.2"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"react": ">=16.13.1"
|
||||
},
|
||||
"peerDependenciesMeta": {
|
||||
"react-dom": {
|
||||
"optional": true
|
||||
},
|
||||
"react-native": {
|
||||
"optional": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"../tslib/react/node_modules/redent": {
|
||||
"version": "3.0.0",
|
||||
"dev": true,
|
||||
@@ -12089,6 +12066,7 @@
|
||||
"../tslib/storage": {
|
||||
"name": "@optuna/storage",
|
||||
"version": "0.0.1",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@sqlite.org/sqlite-wasm": "^3.45.1-build1"
|
||||
@@ -12104,6 +12082,7 @@
|
||||
},
|
||||
"../tslib/storage/node_modules/@sqlite.org/sqlite-wasm": {
|
||||
"version": "3.45.1-build1",
|
||||
"dev": true,
|
||||
"license": "Apache-2.0",
|
||||
"bin": {
|
||||
"sqlite-wasm": "bin/index.js"
|
||||
|
||||
@@ -5,7 +5,6 @@ import {
|
||||
FeedbackComponentType,
|
||||
FormWidgets,
|
||||
Note,
|
||||
ParamImportance,
|
||||
PlotlyGraphObject,
|
||||
PreferenceFeedbackMode,
|
||||
PreferenceHistory,
|
||||
@@ -113,7 +112,7 @@ export type UploadArtifactAPIResponse = {
|
||||
}
|
||||
|
||||
export interface ParamImportancesResponse {
|
||||
param_importances: ParamImportance[][]
|
||||
param_importances: Optuna.ParamImportance[][]
|
||||
}
|
||||
|
||||
export type PlotResponse = {
|
||||
@@ -228,7 +227,9 @@ export abstract class APIClient {
|
||||
trialId: number,
|
||||
user_attrs: { [key: string]: number | string }
|
||||
): Promise<void>
|
||||
abstract getParamImportances(studyId: number): Promise<ParamImportance[][]>
|
||||
abstract getParamImportances(
|
||||
studyId: number
|
||||
): Promise<Optuna.ParamImportance[][]>
|
||||
abstract reportPreference(
|
||||
studyId: number,
|
||||
candidates: number[],
|
||||
|
||||
@@ -15,7 +15,6 @@ import {
|
||||
} from "./apiClient"
|
||||
import {
|
||||
FeedbackComponentType,
|
||||
ParamImportance,
|
||||
StudyDetail,
|
||||
StudySummary,
|
||||
Trial,
|
||||
@@ -230,7 +229,9 @@ export class AxiosClient extends APIClient {
|
||||
.then(() => {
|
||||
return
|
||||
})
|
||||
getParamImportances = (studyId: number): Promise<ParamImportance[][]> =>
|
||||
getParamImportances = (
|
||||
studyId: number
|
||||
): Promise<Optuna.ParamImportance[][]> =>
|
||||
this.axiosInstance
|
||||
.get<ParamImportancesResponse>(
|
||||
`/api/studies/${studyId}/param_importances`
|
||||
|
||||
@@ -67,5 +67,5 @@ export const ArtifactCardMedia: FC<{
|
||||
/>
|
||||
)
|
||||
}
|
||||
return <InsertDriveFileIcon sx={{ fontSize: 80 }} />
|
||||
return <InsertDriveFileIcon sx={{ fontSize: 80, flexGrow: 1 }} />
|
||||
}
|
||||
|
||||
@@ -41,6 +41,15 @@ export const StudyArtifactCards: FC<{ study: StudyDetail }> = ({ study }) => {
|
||||
|
||||
const width = "200px"
|
||||
const height = "150px"
|
||||
const artifacts = [...study.artifacts].sort((a, b) => {
|
||||
if (a.filename < b.filename) {
|
||||
return -1
|
||||
} else if (a.filename > b.filename) {
|
||||
return 1
|
||||
} else {
|
||||
return 0
|
||||
}
|
||||
})
|
||||
|
||||
return (
|
||||
<>
|
||||
@@ -48,7 +57,7 @@ export const StudyArtifactCards: FC<{ study: StudyDetail }> = ({ study }) => {
|
||||
component="div"
|
||||
sx={{ display: "flex", flexWrap: "wrap", p: theme.spacing(1, 0) }}
|
||||
>
|
||||
{study.artifacts.map((artifact) => {
|
||||
{artifacts.map((artifact) => {
|
||||
const urlPath = `/artifacts/${study.id}/${artifact.artifact_id}`
|
||||
return (
|
||||
<Card
|
||||
|
||||
@@ -55,7 +55,8 @@ export const TableArtifactViewer: React.FC<TableArtifactViewerProps> = (
|
||||
})
|
||||
const keys = Array.from(unionSet)
|
||||
return keys.map((key) => ({
|
||||
header: key,
|
||||
// ``header`` cannot be a falsy value, so replace key with a string looking like an empty string.
|
||||
header: key || " ",
|
||||
accessorFn: (info: Data) =>
|
||||
typeof info[key] === "object" ? JSON.stringify(info[key]) : info[key],
|
||||
enableSorting: true,
|
||||
@@ -142,6 +143,7 @@ const loadCSV = (props: TableArtifactViewerProps): Promise<Data[]> => {
|
||||
Papa.parse(props.src, {
|
||||
header: true,
|
||||
download: true,
|
||||
skipEmptyLines: true,
|
||||
complete: (results: Papa.ParseResult<Data>) => {
|
||||
resolve(results?.data)
|
||||
},
|
||||
|
||||
@@ -24,6 +24,7 @@ import { Trial } from "ts/types/optuna"
|
||||
import { actionCreator } from "../../action"
|
||||
import { ArtifactCardMedia } from "./ArtifactCardMedia"
|
||||
import { useDeleteTrialArtifactDialog } from "./DeleteArtifactDialog"
|
||||
import { isTableArtifact, useTableArtifactModal } from "./TableArtifactViewer"
|
||||
import {
|
||||
isThreejsArtifact,
|
||||
useThreejsArtifactModal,
|
||||
@@ -35,12 +36,23 @@ export const TrialArtifactCards: FC<{ trial: Trial }> = ({ trial }) => {
|
||||
useDeleteTrialArtifactDialog()
|
||||
const [openThreejsArtifactModal, renderThreejsArtifactModal] =
|
||||
useThreejsArtifactModal()
|
||||
const [openTableArtifactModal, renderTableArtifactModal] =
|
||||
useTableArtifactModal()
|
||||
const isArtifactModifiable = (trial: Trial) => {
|
||||
return trial.state === "Running" || trial.state === "Waiting"
|
||||
}
|
||||
|
||||
const width = "200px"
|
||||
const height = "150px"
|
||||
const artifacts = [...trial.artifacts].sort((a, b) => {
|
||||
if (a.filename < b.filename) {
|
||||
return -1
|
||||
} else if (a.filename > b.filename) {
|
||||
return 1
|
||||
} else {
|
||||
return 0
|
||||
}
|
||||
})
|
||||
|
||||
return (
|
||||
<>
|
||||
@@ -54,7 +66,7 @@ export const TrialArtifactCards: FC<{ trial: Trial }> = ({ trial }) => {
|
||||
component="div"
|
||||
sx={{ display: "flex", flexWrap: "wrap", p: theme.spacing(1, 0) }}
|
||||
>
|
||||
{trial.artifacts.map((artifact) => {
|
||||
{artifacts.map((artifact) => {
|
||||
const urlPath = `/artifacts/${trial.study_id}/${trial.trial_id}/${artifact.artifact_id}`
|
||||
return (
|
||||
<Card
|
||||
@@ -63,6 +75,9 @@ export const TrialArtifactCards: FC<{ trial: Trial }> = ({ trial }) => {
|
||||
marginBottom: theme.spacing(2),
|
||||
width: width,
|
||||
margin: theme.spacing(0, 1, 1, 0),
|
||||
display: "flex",
|
||||
flexDirection: "column",
|
||||
alignItems: "center",
|
||||
}}
|
||||
>
|
||||
<ArtifactCardMedia
|
||||
@@ -81,7 +96,7 @@ export const TrialArtifactCards: FC<{ trial: Trial }> = ({ trial }) => {
|
||||
sx={{
|
||||
p: theme.spacing(0.5, 0),
|
||||
flexGrow: 1,
|
||||
wordWrap: "break-word",
|
||||
wordBreak: "break-all",
|
||||
maxWidth: `calc(100% - ${theme.spacing(
|
||||
4 +
|
||||
(isThreejsArtifact(artifact) ? 4 : 0) +
|
||||
@@ -104,6 +119,19 @@ export const TrialArtifactCards: FC<{ trial: Trial }> = ({ trial }) => {
|
||||
<FullscreenIcon />
|
||||
</IconButton>
|
||||
) : null}
|
||||
{isTableArtifact(artifact) ? (
|
||||
<IconButton
|
||||
aria-label="show artifact table"
|
||||
size="small"
|
||||
color="inherit"
|
||||
sx={{ margin: "auto 0" }}
|
||||
onClick={() => {
|
||||
openTableArtifactModal(urlPath, artifact)
|
||||
}}
|
||||
>
|
||||
<FullscreenIcon />
|
||||
</IconButton>
|
||||
) : null}
|
||||
{isArtifactModifiable(trial) ? (
|
||||
<IconButton
|
||||
aria-label="delete artifact"
|
||||
@@ -141,6 +169,7 @@ export const TrialArtifactCards: FC<{ trial: Trial }> = ({ trial }) => {
|
||||
</Box>
|
||||
{renderDeleteArtifactDialog()}
|
||||
{renderThreejsArtifactModal()}
|
||||
{renderTableArtifactModal()}
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -1,10 +1,11 @@
|
||||
import { useTheme } from "@mui/material"
|
||||
import { GraphContainer, PlotEdf, useGraphComponentState } from "@optuna/react"
|
||||
import * as plotly from "plotly.js-dist-min"
|
||||
import React, { FC, useEffect } from "react"
|
||||
import { StudyDetail } from "ts/types/optuna"
|
||||
import { CompareStudiesPlotType } from "../apiClient"
|
||||
import { useAPIClient } from "../apiClientProvider"
|
||||
import { useBackendRender } from "../state"
|
||||
import { useBackendRender, usePlotlyColorTheme } from "../state"
|
||||
|
||||
export const GraphEdf: FC<{
|
||||
studies: StudyDetail[]
|
||||
@@ -13,7 +14,15 @@ export const GraphEdf: FC<{
|
||||
if (useBackendRender()) {
|
||||
return <GraphEdfBackend studies={studies} />
|
||||
} else {
|
||||
return <PlotEdf studies={studies} objectiveId={objectiveId} />
|
||||
const theme = useTheme()
|
||||
const colorTheme = usePlotlyColorTheme(theme.palette.mode)
|
||||
return (
|
||||
<PlotEdf
|
||||
studies={studies}
|
||||
objectiveId={objectiveId}
|
||||
colorTheme={colorTheme}
|
||||
/>
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,16 +1,13 @@
|
||||
import { Box, Card, CardContent, Typography, useTheme } from "@mui/material"
|
||||
import { Box, Card, CardContent } from "@mui/material"
|
||||
import * as plotly from "plotly.js-dist-min"
|
||||
import React, { FC, useEffect } from "react"
|
||||
|
||||
import { ParamImportance, StudyDetail } from "ts/types/optuna"
|
||||
import { PlotImportance } from "@optuna/react"
|
||||
import { StudyDetail } from "ts/types/optuna"
|
||||
import { PlotType } from "../apiClient"
|
||||
import { useParamImportance } from "../hooks/useParamImportance"
|
||||
import { usePlot } from "../hooks/usePlot"
|
||||
import {
|
||||
useBackendRender,
|
||||
usePlotlyColorTheme,
|
||||
useStudyDirections,
|
||||
} from "../state"
|
||||
import { useBackendRender } from "../state"
|
||||
|
||||
const plotDomId = "graph-hyperparameter-importances"
|
||||
|
||||
@@ -19,6 +16,13 @@ export const GraphHyperparameterImportance: FC<{
|
||||
study: StudyDetail | null
|
||||
graphHeight: string
|
||||
}> = ({ studyId, study = null, graphHeight }) => {
|
||||
const numCompletedTrials =
|
||||
study?.trials.filter((t) => t.state === "Complete").length || 0
|
||||
const { importances } = useParamImportance({
|
||||
numCompletedTrials,
|
||||
studyId,
|
||||
})
|
||||
|
||||
if (useBackendRender()) {
|
||||
return (
|
||||
<GraphHyperparameterImportanceBackend
|
||||
@@ -29,11 +33,15 @@ export const GraphHyperparameterImportance: FC<{
|
||||
)
|
||||
} else {
|
||||
return (
|
||||
<GraphHyperparameterImportanceFrontend
|
||||
studyId={studyId}
|
||||
study={study}
|
||||
graphHeight={graphHeight}
|
||||
/>
|
||||
<Card>
|
||||
<CardContent>
|
||||
<PlotImportance
|
||||
study={study}
|
||||
importance={importances}
|
||||
graphHeight={graphHeight}
|
||||
/>
|
||||
</CardContent>
|
||||
</Card>
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -64,100 +72,3 @@ const GraphHyperparameterImportanceBackend: FC<{
|
||||
|
||||
return <Box component="div" id={plotDomId} sx={{ height: graphHeight }} />
|
||||
}
|
||||
|
||||
const GraphHyperparameterImportanceFrontend: FC<{
|
||||
studyId: number
|
||||
study: StudyDetail | null
|
||||
graphHeight: string
|
||||
}> = ({ studyId, study = null, graphHeight }) => {
|
||||
const theme = useTheme()
|
||||
const colorTheme = usePlotlyColorTheme(theme.palette.mode)
|
||||
|
||||
const numCompletedTrials =
|
||||
study?.trials.filter((t) => t.state === "Complete").length || 0
|
||||
const { importances } = useParamImportance({
|
||||
numCompletedTrials,
|
||||
studyId,
|
||||
})
|
||||
const nObjectives = useStudyDirections(studyId)?.length
|
||||
const objectiveNames: string[] =
|
||||
study?.objective_names ||
|
||||
study?.directions.map((d, i) => `Objective ${i}`) ||
|
||||
[]
|
||||
|
||||
useEffect(() => {
|
||||
if (importances !== undefined && nObjectives === importances.length) {
|
||||
plotParamImportance(importances, objectiveNames, colorTheme)
|
||||
}
|
||||
}, [nObjectives, importances, colorTheme])
|
||||
|
||||
return (
|
||||
<Card>
|
||||
<CardContent>
|
||||
<Typography
|
||||
variant="h6"
|
||||
sx={{ margin: "1em 0", fontWeight: theme.typography.fontWeightBold }}
|
||||
>
|
||||
Hyperparameter Importance
|
||||
</Typography>
|
||||
<Box component="div" id={plotDomId} sx={{ height: graphHeight }} />
|
||||
</CardContent>
|
||||
</Card>
|
||||
)
|
||||
}
|
||||
|
||||
const plotParamImportance = (
|
||||
importances: ParamImportance[][],
|
||||
objectiveNames: string[],
|
||||
colorTheme: Partial<Plotly.Template>
|
||||
) => {
|
||||
const layout: Partial<plotly.Layout> = {
|
||||
xaxis: {
|
||||
title: "Hyperparameter Importance",
|
||||
},
|
||||
yaxis: {
|
||||
title: "Hyperparameter",
|
||||
automargin: true,
|
||||
},
|
||||
margin: {
|
||||
l: 50,
|
||||
t: 0,
|
||||
r: 50,
|
||||
b: 50,
|
||||
},
|
||||
barmode: "group",
|
||||
bargap: 0.15,
|
||||
bargroupgap: 0.1,
|
||||
uirevision: "true",
|
||||
template: colorTheme,
|
||||
legend: {
|
||||
x: 1.0,
|
||||
y: 0.95,
|
||||
},
|
||||
}
|
||||
|
||||
if (document.getElementById(plotDomId) === null) {
|
||||
return
|
||||
}
|
||||
const traces: Partial<plotly.PlotData>[] = importances.map(
|
||||
(importance, i) => {
|
||||
const reversed = [...importance].reverse()
|
||||
const importance_values = reversed.map((p) => p.importance)
|
||||
const param_names = reversed.map((p) => p.name)
|
||||
const param_hover_templates = reversed.map(
|
||||
(p) => `${p.name} (${p.distribution}): ${p.importance} <extra></extra>`
|
||||
)
|
||||
return {
|
||||
type: "bar",
|
||||
orientation: "h",
|
||||
name: objectiveNames[i],
|
||||
x: importance_values,
|
||||
y: param_names,
|
||||
text: importance_values.map((v) => String(v.toFixed(2))),
|
||||
textposition: "outside",
|
||||
hovertemplate: param_hover_templates,
|
||||
}
|
||||
}
|
||||
)
|
||||
plotly.react(plotDomId, traces, layout)
|
||||
}
|
||||
|
||||
@@ -1,102 +1,22 @@
|
||||
import { Box, Card, CardContent, Typography, useTheme } from "@mui/material"
|
||||
import * as plotly from "plotly.js-dist-min"
|
||||
import React, { FC, useEffect } from "react"
|
||||
import { Card, CardContent } from "@mui/material"
|
||||
import { PlotIntermediateValues } from "@optuna/react"
|
||||
import React, { FC } from "react"
|
||||
import { Trial } from "ts/types/optuna"
|
||||
import { usePlotlyColorTheme } from "../state"
|
||||
|
||||
const plotDomId = "graph-intermediate-values"
|
||||
|
||||
export const GraphIntermediateValues: FC<{
|
||||
trials: Trial[]
|
||||
includePruned: boolean
|
||||
logScale: boolean
|
||||
}> = ({ trials, includePruned, logScale }) => {
|
||||
const theme = useTheme()
|
||||
const colorTheme = usePlotlyColorTheme(theme.palette.mode)
|
||||
|
||||
useEffect(() => {
|
||||
plotIntermediateValue(trials, colorTheme, false, !includePruned, logScale)
|
||||
}, [trials, colorTheme, includePruned, logScale])
|
||||
|
||||
return (
|
||||
<Card>
|
||||
<CardContent>
|
||||
<Typography
|
||||
variant="h6"
|
||||
sx={{ margin: "1em 0", fontWeight: theme.typography.fontWeightBold }}
|
||||
>
|
||||
Intermediate values
|
||||
</Typography>
|
||||
<Box component="div" id={plotDomId} sx={{ height: "450px" }} />
|
||||
<PlotIntermediateValues
|
||||
trials={trials}
|
||||
includePruned={includePruned}
|
||||
logScale={logScale}
|
||||
/>
|
||||
</CardContent>
|
||||
</Card>
|
||||
)
|
||||
}
|
||||
|
||||
const plotIntermediateValue = (
|
||||
trials: Trial[],
|
||||
colorTheme: Partial<Plotly.Template>,
|
||||
filterCompleteTrial: boolean,
|
||||
filterPrunedTrial: boolean,
|
||||
logScale: boolean
|
||||
) => {
|
||||
if (document.getElementById(plotDomId) === null) {
|
||||
return
|
||||
}
|
||||
|
||||
const layout: Partial<plotly.Layout> = {
|
||||
margin: {
|
||||
l: 50,
|
||||
t: 0,
|
||||
r: 50,
|
||||
b: 0,
|
||||
},
|
||||
yaxis: {
|
||||
title: "Objective Value",
|
||||
type: logScale ? "log" : "linear",
|
||||
},
|
||||
xaxis: {
|
||||
title: "Step",
|
||||
type: "linear",
|
||||
},
|
||||
uirevision: "true",
|
||||
template: colorTheme,
|
||||
legend: {
|
||||
x: 1.0,
|
||||
y: 0.95,
|
||||
},
|
||||
}
|
||||
if (trials.length === 0) {
|
||||
plotly.react(plotDomId, [], layout)
|
||||
return
|
||||
}
|
||||
|
||||
const filteredTrials = trials.filter(
|
||||
(t) =>
|
||||
(!filterCompleteTrial && t.state === "Complete") ||
|
||||
(!filterPrunedTrial &&
|
||||
t.state === "Pruned" &&
|
||||
t.values &&
|
||||
t.values.length > 0) ||
|
||||
t.state === "Running"
|
||||
)
|
||||
const plotData: Partial<plotly.PlotData>[] = filteredTrials.map((trial) => {
|
||||
const isFeasible = trial.constraints.every((c) => c <= 0)
|
||||
return {
|
||||
x: trial.intermediate_values.map((iv) => iv.step),
|
||||
y: trial.intermediate_values.map((iv) => iv.value),
|
||||
marker: { maxdisplayed: 10 },
|
||||
mode: "lines+markers",
|
||||
type: "scatter",
|
||||
name: `trial #${trial.number} ${
|
||||
trial.state === "Running"
|
||||
? "(running)"
|
||||
: !isFeasible
|
||||
? "(infeasible)"
|
||||
: ""
|
||||
}`,
|
||||
...(!isFeasible && { line: { color: "#CCCCCC" } }),
|
||||
}
|
||||
})
|
||||
plotly.react(plotDomId, plotData, layout)
|
||||
}
|
||||
|
||||
@@ -157,7 +157,7 @@ const plotTimeline = (
|
||||
xaxis: {
|
||||
title: "Datetime",
|
||||
type: "date",
|
||||
range: [minDatetime.toISOString(), maxDatetime.toISOString()],
|
||||
range: [minDatetime, maxDatetime],
|
||||
},
|
||||
yaxis: {
|
||||
title: "Trial",
|
||||
@@ -188,7 +188,7 @@ const plotTimeline = (
|
||||
x: runDurations,
|
||||
y: bars.map((b) => b.number),
|
||||
// @ts-ignore: To suppress ts(2322)
|
||||
base: starts.map((s) => s.toISOString()),
|
||||
base: starts,
|
||||
name: state,
|
||||
text: bars.map((b) => makeHovertext(b)),
|
||||
hovertemplate: "%{text}<extra>" + state + "</extra>",
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
import * as Optuna from "@optuna/types"
|
||||
import { useQuery } from "@tanstack/react-query"
|
||||
import { AxiosError } from "axios"
|
||||
import { useSnackbar } from "notistack"
|
||||
import { useEffect } from "react"
|
||||
import { ParamImportance } from "ts/types/optuna"
|
||||
import { useAPIClient } from "../apiClientProvider"
|
||||
|
||||
export const useParamImportance = ({
|
||||
@@ -13,7 +13,7 @@ export const useParamImportance = ({
|
||||
const { enqueueSnackbar } = useSnackbar()
|
||||
|
||||
const { data, isLoading, error } = useQuery<
|
||||
ParamImportance[][],
|
||||
Optuna.ParamImportance[][],
|
||||
AxiosError<{ reason: string }>
|
||||
>({
|
||||
queryKey: ["paramImportance", studyId, numCompletedTrials],
|
||||
|
||||
@@ -21,12 +21,6 @@ export type TrialParam = {
|
||||
distribution: Optuna.Distribution
|
||||
}
|
||||
|
||||
export type ParamImportance = {
|
||||
name: string
|
||||
importance: number
|
||||
distribution: Optuna.Distribution
|
||||
}
|
||||
|
||||
export type SearchSpaceItem = {
|
||||
name: string
|
||||
distribution: Optuna.Distribution
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
from __future__ import annotations
|
||||
|
||||
import base64
|
||||
import json
|
||||
import tempfile
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
from __future__ import annotations
|
||||
|
||||
import io
|
||||
import uuid
|
||||
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
from __future__ import annotations
|
||||
|
||||
import io
|
||||
from unittest import TestCase
|
||||
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
from __future__ import annotations
|
||||
|
||||
import io
|
||||
import tempfile
|
||||
from unittest import TestCase
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
from __future__ import annotations
|
||||
|
||||
import io
|
||||
import uuid
|
||||
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
from __future__ import annotations
|
||||
|
||||
import sys
|
||||
from unittest.mock import patch
|
||||
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
from __future__ import annotations
|
||||
|
||||
import itertools
|
||||
from typing import Sequence
|
||||
from typing import Union
|
||||
|
||||
import optuna
|
||||
from optuna_dashboard import ChoiceWidget
|
||||
@@ -60,7 +61,7 @@ for r in range(len(widget_list) + 1):
|
||||
|
||||
@pytest.mark.parametrize("widgets", widgets_combinations_for_user_attr)
|
||||
def test_render_user_attr_form_widgets(
|
||||
widgets: Sequence[Union[ChoiceWidget, SliderWidget, TextInputWidget]],
|
||||
widgets: Sequence[ChoiceWidget | SliderWidget | TextInputWidget],
|
||||
) -> None:
|
||||
study = optuna.create_study()
|
||||
register_user_attr_form_widgets(study, widgets) # type: ignore
|
||||
@@ -77,7 +78,7 @@ for r in range(1, len(widget_list) + 1):
|
||||
|
||||
@pytest.mark.parametrize("widgets", widgets_combinations_for_objective)
|
||||
def test_render_objective_form_widgets(
|
||||
widgets: Sequence[Union[ChoiceWidget, SliderWidget, TextInputWidget]],
|
||||
widgets: Sequence[ChoiceWidget | SliderWidget | TextInputWidget],
|
||||
) -> None:
|
||||
study = optuna.create_study(directions=["maximize"] * len(widgets))
|
||||
register_objective_form_widgets(study, widgets) # type: ignore
|
||||
|
||||
@@ -2,8 +2,6 @@ from __future__ import annotations
|
||||
|
||||
import io
|
||||
import typing
|
||||
from typing import Optional
|
||||
from typing import Union
|
||||
|
||||
from bottle import Bottle
|
||||
from optuna_dashboard._storage import trials_cache
|
||||
@@ -58,9 +56,9 @@ def send_request(
|
||||
app: Bottle,
|
||||
path: str,
|
||||
method: str,
|
||||
body: Union[str, bytes] = b"",
|
||||
queries: Optional[dict[str, str]] = None,
|
||||
headers: Optional[dict[str, str]] = None,
|
||||
body: str | bytes = b"",
|
||||
queries: dict[str, str] | None = None,
|
||||
headers: dict[str, str] | None = None,
|
||||
content_type: str = "text/plain; charset=utf-8",
|
||||
) -> tuple[int, list[tuple[str, str]], bytes]:
|
||||
status: str = ""
|
||||
|
||||
Generated
+2
-3
@@ -56,14 +56,13 @@
|
||||
"@mui/lab": "^5.0.0-alpha.170",
|
||||
"@mui/material": "^5.15.10",
|
||||
"@mui/system": "^5.15.9",
|
||||
"@optuna/storage": "file:../storage",
|
||||
"@tanstack/react-table": "^8.17.3",
|
||||
"plotly.js-dist-min": "^2.30.1",
|
||||
"react": "^18.2.0",
|
||||
"react-dom": "^18.2.0",
|
||||
"recoil": "^0.7.7"
|
||||
"react-dom": "^18.2.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@optuna/storage": "file:../storage",
|
||||
"@optuna/types": "file:../types",
|
||||
"@storybook/addon-essentials": "^8.0.4",
|
||||
"@storybook/addon-interactions": "^8.0.4",
|
||||
|
||||
@@ -180,9 +180,7 @@ export const StudyDetail: FC<{
|
||||
<Grid item xs={6}>
|
||||
<Card sx={{ margin: theme.spacing(2) }}>
|
||||
<CardContent>
|
||||
{!!study && (
|
||||
<PlotImportance study={study} importance={importance} />
|
||||
)}
|
||||
<PlotImportance study={study} importance={importance} />
|
||||
</CardContent>
|
||||
</Card>
|
||||
</Grid>
|
||||
|
||||
@@ -36,6 +36,12 @@ module.exports = {
|
||||
test: /\.wasm$/,
|
||||
type: "asset/inline",
|
||||
},
|
||||
{
|
||||
test: /\.m?js/,
|
||||
resolve: {
|
||||
fullySpecified: false
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
resolve: {
|
||||
|
||||
Generated
+3
-27
@@ -15,14 +15,13 @@
|
||||
"@mui/lab": "^5.0.0-alpha.170",
|
||||
"@mui/material": "^5.15.10",
|
||||
"@mui/system": "^5.15.9",
|
||||
"@optuna/storage": "file:../storage",
|
||||
"@tanstack/react-table": "^8.17.3",
|
||||
"plotly.js-dist-min": "^2.30.1",
|
||||
"react": "^18.2.0",
|
||||
"react-dom": "^18.2.0",
|
||||
"recoil": "^0.7.7"
|
||||
"react-dom": "^18.2.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@optuna/storage": "file:../storage",
|
||||
"@optuna/types": "file:../types",
|
||||
"@storybook/addon-essentials": "^8.0.4",
|
||||
"@storybook/addon-interactions": "^8.0.4",
|
||||
@@ -47,6 +46,7 @@
|
||||
"../storage": {
|
||||
"name": "@optuna/storage",
|
||||
"version": "0.0.1",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@sqlite.org/sqlite-wasm": "^3.45.1-build1"
|
||||
@@ -8409,11 +8409,6 @@
|
||||
"gunzip-maybe": "bin.js"
|
||||
}
|
||||
},
|
||||
"node_modules/hamt_plus": {
|
||||
"version": "1.0.2",
|
||||
"resolved": "https://registry.npmjs.org/hamt_plus/-/hamt_plus-1.0.2.tgz",
|
||||
"integrity": "sha512-t2JXKaehnMb9paaYA7J0BX8QQAY8lwfQ9Gjf4pg/mk4krt+cmwmU652HOoWonf+7+EQV97ARPMhhVgU1ra2GhA=="
|
||||
},
|
||||
"node_modules/handlebars": {
|
||||
"version": "4.7.8",
|
||||
"resolved": "https://registry.npmjs.org/handlebars/-/handlebars-4.7.8.tgz",
|
||||
@@ -11430,25 +11425,6 @@
|
||||
"integrity": "sha512-xNvxJEOUiWPGhUuUdQgAJPKOOJfGnIyKySOc09XkKsgdUV/3E2zvwZYdejjmRgPCgcym1juLH3226yA7sEFJKQ==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/recoil": {
|
||||
"version": "0.7.7",
|
||||
"resolved": "https://registry.npmjs.org/recoil/-/recoil-0.7.7.tgz",
|
||||
"integrity": "sha512-8Og5KPQW9LwC577Vc7Ug2P0vQshkv1y3zG3tSSkWMqkWSwHmE+by06L8JtnGocjW6gcCvfwB3YtrJG6/tWivNQ==",
|
||||
"dependencies": {
|
||||
"hamt_plus": "1.0.2"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"react": ">=16.13.1"
|
||||
},
|
||||
"peerDependenciesMeta": {
|
||||
"react-dom": {
|
||||
"optional": true
|
||||
},
|
||||
"react-native": {
|
||||
"optional": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"node_modules/redent": {
|
||||
"version": "3.0.0",
|
||||
"resolved": "https://registry.npmjs.org/redent/-/redent-3.0.0.tgz",
|
||||
|
||||
@@ -32,15 +32,14 @@
|
||||
"@mui/lab": "^5.0.0-alpha.170",
|
||||
"@mui/material": "^5.15.10",
|
||||
"@mui/system": "^5.15.9",
|
||||
"@optuna/storage": "file:../storage",
|
||||
"@tanstack/react-table": "^8.17.3",
|
||||
"plotly.js-dist-min": "^2.30.1",
|
||||
"react": "^18.2.0",
|
||||
"react-dom": "^18.2.0",
|
||||
"recoil": "^0.7.7"
|
||||
"react-dom": "^18.2.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@optuna/types": "file:../types",
|
||||
"@optuna/storage": "file:../storage",
|
||||
"@storybook/addon-essentials": "^8.0.4",
|
||||
"@storybook/addon-interactions": "^8.0.4",
|
||||
"@storybook/addon-links": "^8.0.4",
|
||||
|
||||
@@ -17,10 +17,13 @@ const getPlotDomId = (objectiveId: number) => `plot-edf-${objectiveId}`
|
||||
export const PlotEdf: FC<{
|
||||
studies: Optuna.Study[]
|
||||
objectiveId: number
|
||||
}> = ({ studies, objectiveId }) => {
|
||||
colorTheme?: Partial<Plotly.Template>
|
||||
}> = ({ studies, objectiveId, colorTheme }) => {
|
||||
const { graphComponentState, notifyGraphDidRender } = useGraphComponentState()
|
||||
|
||||
const theme = useTheme()
|
||||
const colorThemeUsed =
|
||||
colorTheme ?? (theme.palette.mode === "dark" ? plotlyDarkTemplate : {})
|
||||
|
||||
const domId = getPlotDomId(objectiveId)
|
||||
const target = useMemo<Target>(
|
||||
@@ -39,11 +42,11 @@ export const PlotEdf: FC<{
|
||||
// biome-ignore lint/correctness/useExhaustiveDependencies: <explanation>
|
||||
useEffect(() => {
|
||||
if (graphComponentState !== "componentWillMount") {
|
||||
plotEdf(edfPlotInfos, target, domId, theme.palette.mode)?.then(
|
||||
plotEdf(edfPlotInfos, target, domId, colorThemeUsed)?.then(
|
||||
notifyGraphDidRender
|
||||
)
|
||||
}
|
||||
}, [studies, target, theme.palette.mode, graphComponentState])
|
||||
}, [studies, target, colorThemeUsed, graphComponentState])
|
||||
|
||||
return (
|
||||
<Box component="div">
|
||||
@@ -65,14 +68,14 @@ const plotEdf = (
|
||||
edfPlotInfos: EdfPlotInfo[],
|
||||
target: Target,
|
||||
domId: string,
|
||||
mode: string
|
||||
colorTheme: Partial<Plotly.Template>
|
||||
) => {
|
||||
if (document.getElementById(domId) === null) {
|
||||
return
|
||||
}
|
||||
if (edfPlotInfos.length === 0) {
|
||||
return plotly.react(domId, [], {
|
||||
template: mode === "dark" ? plotlyDarkTemplate : {},
|
||||
template: colorTheme,
|
||||
})
|
||||
}
|
||||
|
||||
@@ -90,7 +93,7 @@ const plotEdf = (
|
||||
r: 50,
|
||||
b: 50,
|
||||
},
|
||||
template: mode === "dark" ? plotlyDarkTemplate : {},
|
||||
template: colorTheme,
|
||||
legend: {
|
||||
x: 1.0,
|
||||
y: 0.95,
|
||||
|
||||
@@ -7,19 +7,20 @@ import { plotlyDarkTemplate } from "./PlotlyDarkMode"
|
||||
const plotDomId = "graph-hyperparameter-importances"
|
||||
|
||||
export const PlotImportance: FC<{
|
||||
study: Optuna.Study
|
||||
importance: Optuna.ParamImportance[][]
|
||||
}> = ({ study, importance }) => {
|
||||
study: Optuna.Study | null
|
||||
importance?: Optuna.ParamImportance[][]
|
||||
graphHeight?: string
|
||||
}> = ({ study = null, importance, graphHeight = "450px" }) => {
|
||||
const theme = useTheme()
|
||||
const objectiveNames: string[] = study.directions.map(
|
||||
(_d, i) => `Objective ${i}`
|
||||
)
|
||||
const objectiveNames: string[] = study
|
||||
? study.directions.map((_d, i) => `Objective ${i}`)
|
||||
: []
|
||||
|
||||
useEffect(() => {
|
||||
if (importance.length > 0) {
|
||||
if (study !== null && importance !== undefined && importance.length > 0) {
|
||||
plotParamImportancesBeta(importance, objectiveNames, theme.palette.mode)
|
||||
}
|
||||
}, [objectiveNames, importance, theme.palette.mode])
|
||||
}, [study, objectiveNames, importance, theme.palette.mode])
|
||||
|
||||
return (
|
||||
<>
|
||||
@@ -29,7 +30,7 @@ export const PlotImportance: FC<{
|
||||
>
|
||||
Hyperparameter Importance
|
||||
</Typography>
|
||||
<Box id={plotDomId} sx={{ height: "450px" }} />
|
||||
<Box id={plotDomId} sx={{ height: graphHeight }} />
|
||||
</>
|
||||
)
|
||||
}
|
||||
@@ -58,6 +59,10 @@ const plotParamImportancesBeta = (
|
||||
bargroupgap: 0.1,
|
||||
uirevision: "true",
|
||||
template: mode === "dark" ? plotlyDarkTemplate : {},
|
||||
legend: {
|
||||
x: 1.0,
|
||||
y: 0.95,
|
||||
},
|
||||
}
|
||||
|
||||
if (document.getElementById(plotDomId) === null) {
|
||||
|
||||
@@ -64,6 +64,10 @@ const plotIntermediateValue = (
|
||||
},
|
||||
uirevision: "true",
|
||||
template: mode === "dark" ? plotlyDarkTemplate : {},
|
||||
legend: {
|
||||
x: 1.0,
|
||||
y: 0.95,
|
||||
},
|
||||
}
|
||||
if (trials.length === 0) {
|
||||
plotly.react(plotDomId, [], layout)
|
||||
@@ -79,23 +83,23 @@ const plotIntermediateValue = (
|
||||
t.values.length > 0) ||
|
||||
t.state === "Running"
|
||||
)
|
||||
|
||||
const plotData: Partial<plotly.PlotData>[] = filteredTrials.map((trial) => {
|
||||
const values = trial.intermediate_values.filter(
|
||||
(iv) =>
|
||||
iv.value !== Infinity &&
|
||||
iv.value !== -Infinity &&
|
||||
!Number.isNaN(iv.value)
|
||||
)
|
||||
const isFeasible = trial.constraints.every((c) => c <= 0)
|
||||
return {
|
||||
x: values.map((iv) => iv.step),
|
||||
y: values.map((iv) => iv.value),
|
||||
x: trial.intermediate_values.map((iv) => iv.step),
|
||||
y: trial.intermediate_values.map((iv) => iv.value),
|
||||
marker: { maxdisplayed: 10 },
|
||||
mode: "lines+markers",
|
||||
type: "scatter",
|
||||
name:
|
||||
trial.state !== "Running"
|
||||
? `trial #${trial.number}`
|
||||
: `trial #${trial.number} (running)`,
|
||||
name: `trial #${trial.number} ${
|
||||
trial.state === "Running"
|
||||
? "(running)"
|
||||
: !isFeasible
|
||||
? "(infeasible)"
|
||||
: ""
|
||||
}`,
|
||||
...(!isFeasible && { line: { color: "#CCCCCC" } }),
|
||||
}
|
||||
})
|
||||
plotly.react(plotDomId, plotData, layout)
|
||||
|
||||
+2
-1
@@ -1,6 +1,7 @@
|
||||
import { JournalFileStorage, SQLite3Storage } from "@optuna/storage"
|
||||
import * as Optuna from "@optuna/types"
|
||||
import { SetterOrUpdater } from "recoil"
|
||||
|
||||
type SetterOrUpdater<T> = (valOrUpdater: ((currVal: T) => T) | T) => void
|
||||
|
||||
const readFile = async (file: File) => {
|
||||
return new Promise<ArrayBuffer>((resolve, reject) => {
|
||||
@@ -1,6 +1,6 @@
|
||||
import fs from "node:fs"
|
||||
import * as Optuna from "@optuna/types"
|
||||
import { loadStorageFromFile } from "../src/utils/loadStorageFromFile"
|
||||
import { loadStorageFromFile } from "./loadStorageFromFile"
|
||||
|
||||
declare global {
|
||||
interface Window {
|
||||
|
||||
Reference in New Issue
Block a user