Use distributions for plots

This commit is contained in:
c-bata
2023-01-06 23:56:07 +09:00
parent 744444d699
commit e77c8daf06
13 changed files with 373 additions and 221 deletions
+80 -5
View File
@@ -6,7 +6,7 @@ from typing import TYPE_CHECKING
from typing import Union
import numpy as np
from optuna.distributions import BaseDistribution
from optuna.distributions import BaseDistribution, CategoricalDistribution
from optuna.distributions import FloatDistribution
from optuna.distributions import IntDistribution
from optuna.study import StudySummary
@@ -42,6 +42,41 @@ if TYPE_CHECKING:
},
)
FloatDistributionJSON = TypedDict(
"FloatDistributionJSON",
{
"type": Literal["FloatDistribution"],
"low": float,
"high": float,
"step": float,
"log": bool,
},
)
IntDistributionJSON = TypedDict(
"IntDistributionJSON",
{
"type": Literal["IntDistribution"],
"low": int,
"high": int,
"step": int,
"log": bool,
},
)
CategoricalDistributionChoiceJSON = TypedDict(
"CategoricalDistributionChoiceJSON",
{
"pytype": str,
"value": str,
}
)
CategoricalDistributionJSON = TypedDict(
"CategoricalDistributionJSON",
{
"choices": list[CategoricalDistributionChoiceJSON]
},
)
DistributionJSON = Union[FloatDistributionJSON, IntDistributionJSON, CategoricalDistributionJSON]
MAX_ATTR_LENGTH = 1024
@@ -111,12 +146,22 @@ def serialize_study_detail(
def serialize_frozen_trial(
study_id: int, trial: FrozenTrial, study_system_attrs: dict[str, Any]
) -> dict[str, Any]:
params = []
for param_name, param_external_value in trial.params.items():
distribution = trial.distributions[param_name]
params.append({
"name": param_name,
"param_internal_value": distribution.to_internal_repr(param_external_value),
"param_external_value": str(param_external_value),
"param_external_pytyp": str(type(param_external_value)),
"distribution": serialize_distribution(distribution)
})
serialized = {
"trial_id": trial._trial_id,
"study_id": study_id,
"number": trial.number,
"state": trial.state.name.capitalize(),
"params": [{"name": name, "value": str(value)} for name, value in trial.params.items()],
"params": params,
"user_attrs": serialize_attrs(trial.user_attrs),
"system_attrs": serialize_attrs(getattr(trial, "_system_attrs", {})),
"note": note.get_note_from_system_attrs(study_system_attrs, trial._trial_id),
@@ -160,6 +205,38 @@ def serialize_frozen_trial(
return serialized
def serialize_distribution(distribution: BaseDistribution) -> DistributionJSON:
distribution = normalize_distribution(distribution)
if isinstance(distribution, FloatDistribution):
return {
"type": "FloatDistribution",
"low": distribution.low,
"high": distribution.high,
"step": distribution.step,
"log": distribution.log,
}
if isinstance(distribution, IntDistribution):
return {
"type": "IntDistribution",
"low": distribution.low,
"high": distribution.high,
"step": distribution.step,
"log": distribution.log,
}
if isinstance(distribution, CategoricalDistribution):
return {
"type": "CategoricalDistribution",
"choices": [
{
"pytype": str(type(choice)),
"value": str(choice)
}
for choice in distribution.choices
],
}
raise ValueError(f"Unexpected distribution {str(distribution)}")
def normalize_distribution(distribution: BaseDistribution) -> BaseDistribution:
if distribution.__class__.__name__ == "UniformDistribution":
return FloatDistribution(
@@ -200,12 +277,10 @@ def serialize_search_space(
) -> list[dict[str, Any]]:
serialized = []
for param_name, distribution in search_space:
distribution = normalize_distribution(distribution)
serialized.append(
{
"name": param_name,
"distribution": distribution.__class__.__name__,
"attributes": distribution._asdict(),
"distribution": serialize_distribution(distribution),
}
)
return serialized
+2 -2
View File
@@ -44,8 +44,8 @@ interface StudyDetailResponse {
directions: StudyDirection[]
trials: TrialResponse[]
best_trials: TrialResponse[]
intersection_search_space: SearchSpace[]
union_search_space: SearchSpace[]
intersection_search_space: SearchSpaceItem[]
union_search_space: SearchSpaceItem[]
union_user_attrs: AttributeSpec[]
has_intermediate_values: boolean
note: Note
@@ -41,7 +41,10 @@ export const BestTrialsCard: FC<{
</Typography>
<Typography>
Params = [
{bestTrial.params.map((p) => `${p.name}: ${p.value}`).join(", ")}]
{bestTrial.params
.map((p) => `${p.name}: ${p.param_external_value}`)
.join(", ")}
]
</Typography>
<Typography>
Intermediate Values = [
@@ -101,7 +104,7 @@ export const BestTrialsCard: FC<{
<Typography>
Params = [
{trial.params
.map((p) => `${p.name}: ${p.value}`)
.map((p) => `${p.name}: ${p.param_external_value}`)
.join(", ")}
]
</Typography>
+117 -88
View File
@@ -1,5 +1,5 @@
import * as plotly from "plotly.js-dist-min"
import React, { FC, useEffect, useState } from "react"
import React, { FC, useEffect, useMemo, useState } from "react"
import {
Grid,
FormControl,
@@ -33,31 +33,44 @@ type AxisInfo = {
const PADDING_RATIO = 0.05
const plotDomId = "graph-contour"
const useSearchSpace = (
unionSearchSpaces?: SearchSpaceItem[]
): SearchSpaceItem[] =>
useMemo(
() =>
Array.from(unionSearchSpaces || []).sort((a, b) =>
a.name > b.name ? 1 : a.name < b.name ? -1 : 0
),
[unionSearchSpaces]
)
export const Contour: FC<{
study: StudyDetail | null
}> = ({ study = null }) => {
const theme = useTheme()
const [objectiveId, setObjectiveId] = useState<number>(0)
const [xParam, setXParam] = useState("")
const [yParam, setYParam] = useState("")
const paramNames = study?.union_search_space.map((s) => s.name)
const searchSpaces = useSearchSpace(study?.union_search_space)
const [xParam, setXParam] = useState<SearchSpaceItem | null>(null)
const [yParam, setYParam] = useState<SearchSpaceItem | null>(null)
const objectiveNames: string[] = study?.objective_names || []
if (!xParam && paramNames && paramNames.length > 0) {
setXParam(paramNames[0])
if (xParam === null && searchSpaces.length > 0) {
setXParam(searchSpaces[0])
}
if (!yParam && paramNames && paramNames.length > 1) {
setYParam(paramNames[1])
if (yParam === null && searchSpaces.length > 1) {
setYParam(searchSpaces[1])
}
const handleObjectiveChange = (event: SelectChangeEvent<number>) => {
setObjectiveId(event.target.value as number)
}
const handleXParamChange = (event: SelectChangeEvent<string>) => {
setXParam(event.target.value as string)
const param = searchSpaces.find((s) => s.name === event.target.value)
setXParam(param || null)
}
const handleYParamChange = (event: SelectChangeEvent<string>) => {
setYParam(event.target.value as string)
const param = searchSpaces.find((s) => s.name === event.target.value)
setYParam(param || null)
}
useEffect(() => {
@@ -66,7 +79,7 @@ export const Contour: FC<{
}
}, [study, objectiveId, xParam, yParam, theme.palette.mode])
const space: SearchSpace[] = study ? study.union_search_space : []
const space: SearchSpaceItem[] = study ? study.union_search_space : []
return (
<Grid container direction="row">
@@ -98,7 +111,7 @@ export const Contour: FC<{
<Grid container direction="column" gap={1}>
<FormControl component="fieldset" fullWidth>
<FormLabel component="legend">x:</FormLabel>
<Select value={xParam} onChange={handleXParamChange}>
<Select value={xParam?.name || ""} onChange={handleXParamChange}>
{space.map((d, i) => (
<MenuItem value={d.name} key={d.name}>
{d.name}
@@ -108,7 +121,7 @@ export const Contour: FC<{
</FormControl>
<FormControl component="fieldset" fullWidth>
<FormLabel component="legend">y:</FormLabel>
<Select value={yParam} onChange={handleYParamChange}>
<Select value={yParam?.name || ""} onChange={handleYParamChange}>
{space.map((d, i) => (
<MenuItem value={d.name} key={d.name}>
{d.name}
@@ -126,73 +139,6 @@ export const Contour: FC<{
)
}
const isNumerical = (trials: Trial[], paramName: string): boolean => {
return trials.every((t) => {
const param = t.params.find((param) => param.name === paramName)
if (!param) return true
const val = param.value
return typeof (Number(val) || val) === "number"
})
}
const getAxisInfo = (trials: Trial[], paramName: string): AxisInfo => {
const values = trials.map((trial) => {
const param = trial.params.find((p) => p.name === paramName)
return param ? Number(param.value) || param.value : null
})
let min: number
let max: number
let isLog: boolean
let isCat: boolean
if (isNumerical(trials, paramName)) {
const minValue = Math.min(...(values as number[]))
const maxValue = Math.max(...(values as number[]))
const padding = (maxValue - minValue) * PADDING_RATIO
min = minValue - padding
max = maxValue + padding
isLog = false
isCat = false
} else {
const uniqueValues = unique(values)
const span = uniqueValues.length - (uniqueValues.includes(null) ? 2 : 1)
const padding = span * PADDING_RATIO
min = -padding
max = span + padding
isLog = false
isCat = true
}
const indices = isNumerical(trials, paramName)
? unique((values as (number | null)[]).filter((v) => v !== null)).sort(
(a, b) => a - b
)
: unique((values as (string | null)[]).filter((v) => v !== null)).sort(
(a, b) =>
a.toString().toLowerCase() < b.toString().toLowerCase()
? -1
: a.toString().toLowerCase() > b.toString().toLowerCase()
? 1
: 0
)
if (indices.length >= 2 && isNumerical(trials, paramName)) {
indices.unshift(min)
indices.push(max)
}
return {
name: paramName,
min,
max,
isLog,
isCat,
indices,
values,
}
}
const filterFunc = (trial: Trial, objectiveId: number): boolean => {
return (
trial.state === "Complete" &&
@@ -205,8 +151,8 @@ const filterFunc = (trial: Trial, objectiveId: number): boolean => {
const plotContour = (
study: StudyDetail,
objectiveId: number,
xParam: string,
yParam: string,
xParam: SearchSpaceItem | null,
yParam: SearchSpaceItem | null,
mode: string
) => {
if (document.getElementById(plotDomId) === null) {
@@ -215,16 +161,15 @@ const plotContour = (
const trials: Trial[] = study ? study.trials : []
const filteredTrials = trials.filter((t) => filterFunc(t, objectiveId))
if (filteredTrials.length === 0) {
if (filteredTrials.length === 0 || xParam === null || yParam === null) {
plotly.react(plotDomId, [], {
template: mode === "dark" ? plotlyDarkTemplate : {},
})
return
}
const xAxis = getAxisInfo(trials, xParam)
const yAxis = getAxisInfo(trials, yParam)
const xAxis = getAxisInfo(study, trials, xParam)
const yAxis = getAxisInfo(study, trials, yParam)
const xIndices = xAxis.indices
const yIndices = yAxis.indices
@@ -279,11 +224,11 @@ const plotContour = (
const layout: Partial<plotly.Layout> = {
xaxis: {
title: xParam,
title: xParam.name,
type: xAxis.isCat ? "category" : undefined,
},
yaxis: {
title: yParam,
title: yParam.name,
type: yAxis.isCat ? "category" : undefined,
},
margin: {
@@ -296,3 +241,87 @@ const plotContour = (
}
plotly.react(plotDomId, plotData, layout)
}
const getAxisInfoForNumericalParams = (
trials: Trial[],
paramName: string,
distribution: FloatDistribution | IntDistribution
): AxisInfo => {
const padding = (distribution.high - distribution.low) * PADDING_RATIO
const min = distribution.low - padding
const max = distribution.high + padding
const values = trials.map(
(trial) =>
trial.params.find((p) => p.name === paramName)?.param_internal_value ||
null
)
const indices = unique(values)
.filter((v) => v !== null)
.sort((a, b) => a - b)
if (indices.length >= 2) {
indices.unshift(min)
indices.push(max)
}
return {
name: paramName,
min,
max,
isLog: distribution.log,
isCat: false,
indices,
values,
}
}
const getAxisInfoForCategoricalParams = (
trials: Trial[],
paramName: string,
distribution: CategoricalDistribution
): AxisInfo => {
const values = trials.map(
(trial) =>
trial.params.find((p) => p.name === paramName)?.param_external_value ||
null
)
const isDynamic = values.some((v) => v === null)
const span = distribution.choices.length - (isDynamic ? 2 : 1)
const padding = span * PADDING_RATIO
const min = -padding
const max = span + padding
const indices = distribution.choices
.map((c) => c.value)
.sort((a, b) =>
a.toLowerCase() < b.toLowerCase()
? -1
: a.toLowerCase() > b.toLowerCase()
? 1
: 0
)
return {
name: paramName,
min,
max,
isLog: false,
isCat: true,
indices,
values,
}
}
const getAxisInfo = (
study: StudyDetail,
trials: Trial[],
param: SearchSpaceItem
): AxisInfo => {
if (param.distribution.type === "CategoricalDistribution") {
return getAxisInfoForCategoricalParams(
trials,
param.name,
param.distribution
)
} else {
return getAxisInfoForNumericalParams(trials, param.name, param.distribution)
}
}
@@ -16,7 +16,11 @@ import {
useTheme,
} from "@mui/material"
import { plotlyDarkTemplate } from "./PlotlyDarkMode"
import { useFilteredTrials, Target, useTargetList } from "../trialFilter"
import {
useFilteredTrials,
Target,
useObjectiveAndSystemAttrTargets,
} from "../trialFilter"
const plotDomId = "graph-history"
@@ -30,11 +34,11 @@ export const GraphHistory: FC<{
const [filterPrunedTrial, setFilterPrunedTrial] = useState<boolean>(false)
const objectiveNames: string[] = study?.objective_names || []
const targetList = useTargetList(study)
const targetList = useObjectiveAndSystemAttrTargets(study)
const [targetIndex, setTargetIndex] = useState<number>(0)
const trials = useFilteredTrials(
study,
targetList[targetIndex],
[targetList[targetIndex]],
filterCompleteTrial,
filterPrunedTrial
)
@@ -136,15 +136,10 @@ const plotCoordinate = (
},
]
study.intersection_search_space.forEach((s) => {
const valueStrings = filteredTrials.map((t) => {
const param = t.params.find((p) => p.name === s.name)
return param!.value
})
const isnum = valueStrings.every((v) => {
return !isNaN(Number(v))
})
if (isnum) {
const values: number[] = valueStrings.map((v) => parseFloat(v))
const values: number[] = filteredTrials.map(
(t) => t.params.find((p) => p.name === s.name)!.param_internal_value
)
if (s.distribution.type !== "CategoricalDistribution") {
dimensions.push({
label: breakLabelIfTooLong(s.name),
values: values,
@@ -152,11 +147,7 @@ const plotCoordinate = (
})
} else {
// categorical
const vocabSet = new Set<string>(valueStrings)
const vocabArr = Array.from<string>(vocabSet)
const values: number[] = valueStrings.map((v) =>
vocabArr.findIndex((vocab) => v === vocab)
)
const vocabArr: string[] = s.distribution.choices.map((c) => c.value)
const tickvals: number[] = vocabArr.map((v, i) => i)
dimensions.push({
label: breakLabelIfTooLong(s.name),
@@ -105,7 +105,7 @@ const makeHovertext = (trial: Trial): string => {
number: trial.number,
values: trial.values,
params: trial.params
.map((p) => [p.name, p.value])
.map((p) => [p.name, p.param_external_value])
.reduce((obj, [key, value]) => ({ ...obj, [key]: value }), {}),
},
undefined,
+70 -79
View File
@@ -1,5 +1,5 @@
import * as plotly from "plotly.js-dist-min"
import React, { ChangeEvent, FC, useEffect, useState } from "react"
import React, { ChangeEvent, FC, useEffect, useMemo, useState } from "react"
import {
Grid,
FormControl,
@@ -13,52 +13,76 @@ import {
Box,
} from "@mui/material"
import { plotlyDarkTemplate } from "./PlotlyDarkMode"
import { Target, useFilteredTrials, useObjectiveTargets } from "../trialFilter"
import { useSnackbar } from "notistack"
const plotDomId = "graph-slice"
// TODO(c-bata): Check `log` field of IntDistribution and FloatDistribution.
const logDistributions = ["LogUniformDistribution", "IntLogUniformDistribution"]
const useSearchSpace = (
unionSearchSpaces?: SearchSpaceItem[]
): SearchSpaceItem[] =>
useMemo(
() =>
Array.from(unionSearchSpaces || []).sort((a, b) =>
a.name > b.name ? 1 : a.name < b.name ? -1 : 0
),
[unionSearchSpaces]
)
const isLogScale = (s: SearchSpaceItem): boolean => {
if (s.distribution.type === "CategoricalDistribution") {
return false
}
return s.distribution.log
}
export const GraphSlice: FC<{
study: StudyDetail | null
}> = ({ study = null }) => {
const theme = useTheme()
const trials: Trial[] = study !== null ? study.trials : []
const { enqueueSnackbar } = useSnackbar()
const [objectiveId, setObjectiveId] = useState<number>(0)
const [selected, setSelected] = useState<string | null>(null)
const [logXScale, setLogXScale] = useState<boolean>(false)
const [selected, setSelected] = useState<SearchSpaceItem | null>(null)
const [logYScale, setLogYScale] = useState<boolean>(false)
const paramNames = study?.union_search_space.map((s) => s.name)
const distributions = new Map(
study?.union_search_space.map((s) => [s.name, s.distribution])
)
const searchSpaces = useSearchSpace(study?.union_search_space)
const targets = useObjectiveTargets(study)
const filterTargets: Target[] = [targets[objectiveId]]
if (selected !== null) filterTargets.push(new Target("params", selected.name))
const trials = useFilteredTrials(study, filterTargets, false, false)
const objectiveNames: string[] = study?.objective_names || []
if (selected === null && paramNames && paramNames.length > 0) {
const distribution = distributions.get(paramNames[0]) || ""
setSelected(paramNames[0])
setLogXScale(logDistributions.includes(distribution))
if (selected === null && searchSpaces.length > 0) {
setSelected(searchSpaces[0])
}
useEffect(() => {
plotSlice(
trials,
objectiveId,
targets[objectiveId],
selected,
logXScale,
logYScale,
theme.palette.mode
)
}, [trials, objectiveId, selected, logXScale, logYScale, theme.palette.mode])
}, [trials, targets[objectiveId], selected, logYScale, theme.palette.mode])
const handleObjectiveChange = (event: SelectChangeEvent<number>) => {
setObjectiveId(event.target.value as number)
}
const handleSelectedParam = (e: SelectChangeEvent<string>) => {
const paramName = e.target.value
const distribution = distributions.get(paramName) || ""
setSelected(paramName)
setLogXScale(logDistributions.includes(distribution))
const s = searchSpaces.find((s) => s.name === e.target.value)
if (s === undefined) {
enqueueSnackbar(
`Cannot find ${e.target.value} param in the search space.`,
{
variant: "error",
}
)
return
}
setSelected(s)
}
const handleLogYScaleChange = (e: ChangeEvent<HTMLInputElement>) => {
@@ -93,9 +117,9 @@ export const GraphSlice: FC<{
)}
<FormControl component="fieldset">
<FormLabel component="legend">Parameter:</FormLabel>
<Select value={selected || ""} onChange={handleSelectedParam}>
{paramNames?.map((p, i) => (
<MenuItem value={p} key={i}>
<Select value={selected?.name || ""} onChange={handleSelectedParam}>
{searchSpaces?.map((s, i) => (
<MenuItem value={s.name} key={i}>
{objectiveNames.length === study?.directions.length
? objectiveNames[i]
: `${i}`}
@@ -119,32 +143,10 @@ export const GraphSlice: FC<{
)
}
const filterFunc = (
trial: Trial,
objectiveId: number,
selected: string | null
): boolean => {
if (trial.state !== "Complete" && trial.state !== "Pruned") {
return false
}
if (trial.params.find((p) => p.name == selected) === undefined) {
return false
}
if (trial.values === undefined) {
return false
}
return (
trial.values.length > objectiveId &&
trial.values[objectiveId] !== "inf" &&
trial.values[objectiveId] !== "-inf"
)
}
const plotSlice = (
trials: Trial[],
objectiveId: number,
selected: string | null,
logXScale: boolean,
target: Target,
selected: SearchSpaceItem | null,
logYScale: boolean,
mode: string
) => {
@@ -160,8 +162,8 @@ const plotSlice = (
b: 0,
},
xaxis: {
title: selected || "",
type: logXScale ? "log" : "linear",
title: selected?.name || "",
type: selected !== null && isLogScale(selected) ? "log" : "linear",
gridwidth: 1,
automargin: true,
},
@@ -174,34 +176,27 @@ const plotSlice = (
showlegend: false,
template: mode === "dark" ? plotlyDarkTemplate : {},
}
const filteredTrials = trials.filter((t) =>
filterFunc(t, objectiveId, selected)
)
if (filteredTrials.length === 0 || selected === null) {
if (selected === null) {
plotly.react(plotDomId, [], layout)
return
}
if (trials.length === 0) {
plotly.react(plotDomId, [], layout)
return
}
const objectiveValues: number[] = filteredTrials.map(
(t) => t.values![objectiveId] as number
const objectiveValues: number[] = trials.map(
(t) => target.getTargetValue(t) as number
)
const valueStrings = filteredTrials.map((t) => {
return t.params.find((p) => p.name == selected)!.value
})
const paramTarget = new Target("params", selected.name)
const values = trials.map((t) => paramTarget.getTargetValue(t) as number)
const trialNumbers: number[] = filteredTrials.map((t) => t.number)
const isnum = valueStrings.every((v) => {
return !isNaN(Number(v))
})
if (isnum) {
const valuesNum: number[] = valueStrings.map((v) => parseFloat(v))
const trialNumbers: number[] = trials.map((t) => t.number)
if (selected.distribution.type !== "CategoricalDistribution") {
const trace: plotly.Data[] = [
{
type: "scatter",
x: valuesNum,
x: values,
y: objectiveValues,
mode: "markers",
marker: {
@@ -219,23 +214,19 @@ const plotSlice = (
},
]
layout["xaxis"] = {
title: selected,
type: logXScale ? "log" : "linear",
title: selected.name,
type: selected.distribution.log ? "log" : "linear",
gridwidth: 1,
automargin: true, // Otherwise the label is outside of the plot
}
plotly.react(plotDomId, trace, layout)
} else {
const vocabSet = new Set<string>(valueStrings)
const vocabArr = Array.from<string>(vocabSet)
const valuesCategorical: number[] = valueStrings.map((v) =>
vocabArr.findIndex((vocab) => v === vocab)
)
const vocabArr = selected.distribution.choices.map((c) => c.value)
const tickvals: number[] = vocabArr.map((v, i) => i)
const trace: plotly.Data[] = [
{
type: "scatter",
x: valuesCategorical,
x: values,
y: objectiveValues,
mode: "markers",
marker: {
@@ -253,8 +244,8 @@ const plotSlice = (
},
]
layout["xaxis"] = {
title: selected,
type: logXScale ? "log" : "linear",
title: selected.name,
type: "linear",
gridwidth: 1,
tickvals: tickvals,
ticktext: vocabArr,
+4 -1
View File
@@ -162,7 +162,10 @@ const TrialListDetail: FC<{
</Typography>
<Typography>
Params = [
{trial.params.map((p) => `${p.name}: ${p.value}`).join(", ")}]
{trial.params
.map((p) => `${p.name}: ${p.param_external_value}`)
.join(", ")}
]
</Typography>
<Typography>
Started At ={" "}
+11 -6
View File
@@ -139,20 +139,23 @@ export const TrialTable: FC<{
studyDetail?.intersection_search_space.length
) {
studyDetail?.intersection_search_space.forEach((s) => {
const sortable = s.distribution !== "CategoricalDistribution"
const filterable = s.distribution === "CategoricalDistribution"
const sortable = s.distribution.type !== "CategoricalDistribution"
const filterable = s.distribution.type === "CategoricalDistribution"
columns.push({
field: "params",
label: `Param ${s.name}`,
toCellValue: (i) =>
trials[i].params.find((p) => p.name === s.name)?.value || null,
trials[i].params.find((p) => p.name === s.name)
?.param_external_value || null,
sortable: sortable,
filterable: filterable,
less: (firstEl, secondEl): number => {
const firstVal = firstEl.params.find((p) => p.name === s.name)?.value
const firstVal = firstEl.params.find(
(p) => p.name === s.name
)?.param_internal_value
const secondVal = secondEl.params.find(
(p) => p.name === s.name
)?.value
)?.param_internal_value
if (firstVal === secondVal) {
return 0
@@ -171,7 +174,9 @@ export const TrialTable: FC<{
field: "params",
label: "Params",
toCellValue: (i) =>
trials[i].params.map((p) => p.name + ": " + p.value).join(", "),
trials[i].params
.map((p) => p.name + ": " + p.param_external_value)
.join(", "),
})
}
+35 -9
View File
@@ -1,10 +1,12 @@
import { useMemo } from "react"
type TargetKind = "objective" | "user_attr" | "params"
export class Target {
kind: "objective" | "user_attr"
kind: TargetKind
key: number | string
constructor(kind: "objective" | "user_attr", key: number | string) {
constructor(kind: TargetKind, key: number | string) {
this.kind = kind
this.key = key
}
@@ -18,8 +20,10 @@ export class Target {
if (typeof this.key !== "string") {
return false
}
} else {
return false
} else if (this.kind === "params") {
if (typeof this.key !== "string") {
return false
}
}
return true
}
@@ -31,12 +35,17 @@ export class Target {
return objectiveNames[objectiveId]
}
return `Objective ${objectiveId}`
} else {
} else if (this.kind === "user_attr") {
return `User Attribute ${this.key}`
} else {
return `Param ${this.key}`
}
}
getObjectiveId(): number | null {
if (this.kind !== "objective") {
return null
}
return this.key as number
}
@@ -68,6 +77,12 @@ export class Target {
return null
}
return value
} else if (this.kind === "params") {
const param = trial.params.find((p) => p.name === this.key)
if (param === undefined) {
return null
}
return param.param_internal_value
}
return null
}
@@ -75,7 +90,7 @@ export class Target {
export const useFilteredTrials = (
study: StudyDetail | null,
target: Target,
targets: Target[],
filterComplete: boolean,
filterPruned: boolean
): Trial[] =>
@@ -93,11 +108,22 @@ export const useFilteredTrials = (
if (t.state === "Pruned" && filterPruned) {
return false
}
return target.getTargetValue(t) !== null
return targets.every((target) => target.getTargetValue(t) !== null)
})
}, [study?.trials, target, filterComplete, filterPruned])
}, [study?.trials, targets, filterComplete, filterPruned])
export const useTargetList = (study: StudyDetail | null): Target[] =>
export const useObjectiveTargets = (study: StudyDetail | null): Target[] =>
useMemo<Target[]>(() => {
if (study !== null) {
return study.directions.map((v, i) => new Target("objective", i))
} else {
return [new Target("objective", 0)]
}
}, [study?.directions])
export const useObjectiveAndSystemAttrTargets = (
study: StudyDetail | null
): Target[] =>
useMemo<Target[]>(() => {
if (study !== null) {
return [
+32 -7
View File
@@ -11,10 +11,32 @@ type TrialValueNumber = number | "inf" | "-inf"
type TrialIntermediateValueNumber = number | "inf" | "-inf" | "nan"
type TrialState = "Running" | "Complete" | "Pruned" | "Fail" | "Waiting"
type StudyDirection = "maximize" | "minimize" | "not_set"
type FloatDistribution = {
type: "FloatDistribution"
low: number
high: number
step: number
log: boolean
}
type IntDistribution = {
type: "IntDistribution"
low: number
high: number
step: number
log: boolean
}
type CategoricalDistribution = {
type: "CategoricalDistribution"
choices: { pytype: string; value: string }[]
}
type Distribution =
| "FloatDistribution"
| "IntDistribution"
| "CategoricalDistribution"
| FloatDistribution
| IntDistribution
| CategoricalDistribution
type GraphVisibility = {
history: boolean
@@ -34,7 +56,10 @@ type TrialIntermediateValue = {
type TrialParam = {
name: string
value: string
param_internal_value: number
param_external_value: string
param_external_type: string
distribution: Distribution
}
type ParamImportance = {
@@ -43,7 +68,7 @@ type ParamImportance = {
distribution: Distribution
}
type SearchSpace = {
type SearchSpaceItem = {
name: string
distribution: Distribution
}
@@ -94,8 +119,8 @@ type StudyDetail = {
datetime_start: Date
best_trials: Trial[]
trials: Trial[]
intersection_search_space: SearchSpace[]
union_search_space: SearchSpace[]
intersection_search_space: SearchSpaceItem[]
union_search_space: SearchSpaceItem[]
union_user_attrs: AttributeSpec[]
has_intermediate_values: boolean
note: Note
+4 -4
View File
@@ -61,21 +61,21 @@ const studyDetail: StudyDetail = {
intersection_search_space: [
{
name: "x",
distribution: "FloatDistribution" as Distribution,
distribution: "FloatDistribution" as DistributionName,
},
{
name: "y",
distribution: "FloatDistribution" as Distribution,
distribution: "FloatDistribution" as DistributionName,
},
],
union_search_space: [
{
name: "x",
distribution: "FloatDistribution" as Distribution,
distribution: "FloatDistribution" as DistributionName,
},
{
name: "y",
distribution: "FloatDistribution" as Distribution,
distribution: "FloatDistribution" as DistributionName,
},
],
union_user_attrs: [