Fix bug when given inf objective value

This commit is contained in:
c-bata
2022-03-12 01:31:29 +09:00
parent 38329d3e29
commit a853ccd01f
10 changed files with 113 additions and 50 deletions
+8 -3
View File
@@ -1,8 +1,10 @@
import json
import math
from typing import Any
from typing import Dict
from typing import List
from typing import Tuple
from typing import Union
from optuna.distributions import BaseDistribution
from optuna.study import StudySummary
@@ -27,7 +29,7 @@ IntermediateValue = TypedDict(
"IntermediateValue",
{
"step": int,
"value": float,
"value": Union[float, str],
},
)
TrialParam = TypedDict(
@@ -53,7 +55,10 @@ def serialize_attrs(attrs: Dict[str, Any]) -> List[Attribute]:
def serialize_intermediate_values(values: Dict[int, float]) -> List[IntermediateValue]:
return [{"step": step, "value": value} for step, value in values.items()]
return [
{"step": step, "value": "inf" if math.isinf(value) else value}
for step, value in values.items()
]
def serialize_trial_params(params: Dict[str, Any]) -> List[TrialParam]:
@@ -120,7 +125,7 @@ def serialize_frozen_trial(study_id: int, trial: FrozenTrial) -> Dict[str, Any]:
}
if trial.values is not None:
serialized["values"] = trial.values
serialized["values"] = ["inf" if math.isinf(v) else v for v in trial.values]
if trial.datetime_start is not None:
serialized["datetime_start"] = trial.datetime_start.isoformat()
+1 -1
View File
@@ -7,7 +7,7 @@ interface TrialResponse {
study_id: number
number: number
state: TrialState
values?: number[]
values?: (number | "inf")[]
intermediate_values: TrialIntermediateValue[]
datetime_start?: string
datetime_complete?: string
@@ -64,15 +64,19 @@ export const Edf: FC<{
)
}
const filterFunc = (trial: Trial, objectiveId: number): boolean => {
return trial.state !== "Complete" || trial.values![objectiveId] !== "inf"
}
const plotEdf = (study: StudyDetail, objectiveId: number, mode: string) => {
if (document.getElementById(plotDomId) === null) {
return
}
const trials: Trial[] = study ? study.trials : []
const completedTrials = trials.filter((t) => t.state === "Complete")
const filteredTrials = trials.filter((t) => filterFunc(t, objectiveId))
if (completedTrials.length === 0) {
if (filteredTrials.length === 0) {
plotly.react(plotDomId, [])
return
}
@@ -80,7 +84,7 @@ const plotEdf = (study: StudyDetail, objectiveId: number, mode: string) => {
const target_name = "Objective Value"
const target = (t: Trial): number => {
return t.values![objectiveId]
return t.values![objectiveId] as number
}
const layout: Partial<plotly.Layout> = {
@@ -99,7 +103,7 @@ const plotEdf = (study: StudyDetail, objectiveId: number, mode: string) => {
template: mode === "dark" ? plotlyDarkTemplate : {},
}
const values = completedTrials.map((t) => target(t))
const values = filteredTrials.map((t) => target(t))
const numValues = values.length
const minX = Math.min(...values)
const maxX = Math.max(...values)
@@ -168,6 +168,18 @@ export const GraphHistory: FC<{
)
}
const filterFunc = (trial: Trial, objectiveId: number): boolean => {
if (trial.state !== "Complete" && trial.state !== "Pruned") {
return false
}
if (trial.values === undefined) {
return false
}
return (
trial.values.length > objectiveId && trial.values[objectiveId] !== "inf"
)
}
const plotHistory = (
study: StudyDetail,
objectiveId: number,
@@ -198,11 +210,7 @@ const plotHistory = (
template: mode === "dark" ? plotlyDarkTemplate : {},
}
let filteredTrials = study.trials.filter(
(t) =>
t.state === "Complete" ||
(t.state === "Pruned" && t.values && t.values.length > 0)
)
let filteredTrials = study.trials.filter((t) => filterFunc(t, objectiveId))
if (filterCompleteTrial) {
filteredTrials = filteredTrials.filter((t) => t.state !== "Complete")
}
@@ -215,22 +223,22 @@ const plotHistory = (
}
const trialsForLinePlot: Trial[] = []
let currentBest: number | null = null
filteredTrials.forEach((item) => {
filteredTrials.forEach((t) => {
if (currentBest === null) {
currentBest = item.values![objectiveId]
trialsForLinePlot.push(item)
currentBest = t.values![objectiveId] as number
trialsForLinePlot.push(t)
} else if (
study.directions[objectiveId] === "maximize" &&
item.values![objectiveId] > currentBest
t.values![objectiveId] > currentBest
) {
currentBest = item.values![objectiveId]
trialsForLinePlot.push(item)
currentBest = t.values![objectiveId] as number
trialsForLinePlot.push(t)
} else if (
study.directions[objectiveId] === "minimize" &&
item.values![objectiveId] < currentBest
t.values![objectiveId] < currentBest
) {
currentBest = item.values![objectiveId]
trialsForLinePlot.push(item)
currentBest = t.values![objectiveId] as number
trialsForLinePlot.push(t)
}
})
@@ -245,14 +253,16 @@ const plotHistory = (
const xForLinePlot = trialsForLinePlot.map(getAxisX)
xForLinePlot.push(getAxisX(filteredTrials[filteredTrials.length - 1]))
const yForLinePlot = trialsForLinePlot.map(
(t: Trial): number => t.values![objectiveId]
(t: Trial): number => t.values![objectiveId] as number
)
yForLinePlot.push(yForLinePlot[yForLinePlot.length - 1])
const plotData: Partial<plotly.PlotData>[] = [
{
x: filteredTrials.map(getAxisX),
y: filteredTrials.map((t: Trial): number => t.values![objectiveId]),
y: filteredTrials.map(
(t: Trial): number => t.values![objectiveId] as number
),
mode: "markers",
type: "scatter",
},
@@ -54,9 +54,10 @@ const plotIntermediateValue = (trials: Trial[], mode: string) => {
(t.state === "Pruned" && t.values && t.values.length > 0)
)
const plotData: Partial<plotly.PlotData>[] = filteredTrials.map((trial) => {
const values = trial.intermediate_values.filter((iv) => iv.value !== "inf")
return {
x: trial.intermediate_values.map((iv) => iv.step),
y: trial.intermediate_values.map((iv) => iv.value),
x: values.map((iv) => iv.step),
y: values.map((iv) => iv.value),
mode: "lines+markers",
type: "scatter",
name: `trial #${trial.number}`,
@@ -65,6 +65,18 @@ export const GraphParallelCoordinate: FC<{
)
}
const filterFunc = (trial: Trial, objectiveId: number): boolean => {
if (trial.state !== "Complete" && trial.state !== "Pruned") {
return false
}
if (trial.values === undefined) {
return false
}
return (
trial.values.length > objectiveId && trial.values[objectiveId] !== "inf"
)
}
const plotCoordinate = (
study: StudyDetail,
objectiveId: number,
@@ -89,11 +101,7 @@ const plotCoordinate = (
return
}
const filteredTrials = study.trials.filter(
(t) =>
t.state === "Complete" ||
(t.state === "Pruned" && t.values && t.values.length > 0)
)
const filteredTrials = study.trials.filter((t) => filterFunc(t, objectiveId))
const maxLabelLength = 40
const breakLength = maxLabelLength / 2
@@ -115,7 +123,7 @@ const plotCoordinate = (
// Intersection param names
const objectiveValues: number[] = filteredTrials.map(
(t) => t.values![objectiveId]
(t) => t.values![objectiveId] as number
)
const dimensions = [
{
@@ -85,6 +85,10 @@ export const GraphParetoFront: FC<{
)
}
const filterFunc = (trial: Trial): boolean => {
return trial.state !== "Complete" || trial.values!.every((v) => v !== "inf")
}
const plotParetoFront = (
study: StudyDetail,
objectiveXId: number,
@@ -106,18 +110,20 @@ const plotParetoFront = (
}
const trials: Trial[] = study ? study.trials : []
const completedTrials = trials.filter((t) => t.state === "Complete")
const filteredTrials = trials.filter(filterFunc)
if (completedTrials.length === 0) {
if (filteredTrials.length === 0) {
plotly.react(plotDomId, [], layout)
return
}
const normalizedValues: number[][] = []
completedTrials.forEach((t) => {
filteredTrials.forEach((t) => {
if (t.values && t.values.length === study.directions.length) {
const trialValues = t.values.map((v: number, i: number) => {
return study.directions[i] === "minimize" ? v : -v
const trialValues = t.values.map((v, i) => {
return study.directions[i] === "minimize"
? (v as number)
: (-v as number)
})
normalizedValues.push(trialValues)
}
@@ -144,11 +150,11 @@ const plotParetoFront = (
const plotData: Partial<plotly.PlotData>[] = [
{
type: "scatter",
x: completedTrials.map((t: Trial): number => {
return t.values![objectiveXId]
x: filteredTrials.map((t: Trial): number => {
return t.values![objectiveXId] as number
}),
y: completedTrials.map((t: Trial): number => {
return t.values![objectiveYId]
y: filteredTrials.map((t: Trial): number => {
return t.values![objectiveYId] as number
}),
mode: "markers",
xaxis: "Objective X",
@@ -156,7 +162,7 @@ const plotParetoFront = (
marker: {
color: pointColors,
},
text: completedTrials.map(
text: filteredTrials.map(
(t: Trial): string => `Trial (number=${t.number})`
),
hovertemplate: "%{text}<extra></extra>",
@@ -129,6 +129,25 @@ 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"
)
}
const plotSlice = (
trials: Trial[],
objectiveId: number,
@@ -164,11 +183,8 @@ const plotSlice = (
template: mode === "dark" ? plotlyDarkTemplate : {},
}
const filteredTrials = trials.filter(
(t) =>
(t.state === "Complete" ||
(t.state === "Pruned" && t.values && t.values.length > 0)) &&
t.params.find((p) => p.name == selected) !== undefined
const filteredTrials = trials.filter((t) =>
filterFunc(t, objectiveId, selected)
)
if (filteredTrials.length === 0 || selected === null) {
@@ -177,7 +193,7 @@ const plotSlice = (
}
const objectiveValues: number[] = filteredTrials.map(
(t) => t.values![objectiveId]
(t) => t.values![objectiveId] as number
)
const valueStrings = filteredTrials.map((t) => {
return t.params.find((p) => p.name == selected)!.value
+2 -2
View File
@@ -21,7 +21,7 @@ type Distribution =
declare interface TrialIntermediateValue {
step: number
value: number
value: number | "inf"
}
declare interface TrialParam {
@@ -50,7 +50,7 @@ declare interface Trial {
study_id: number
number: number
state: TrialState
values?: number[]
values?: (number | "inf")[]
intermediate_values: TrialIntermediateValue[]
datetime_start?: Date
datetime_complete?: Date
+13
View File
@@ -1,5 +1,6 @@
import argparse
import asyncio
import math
import os
import sys
import threading
@@ -84,6 +85,18 @@ def create_dummy_storage() -> optuna.storages.InMemoryStorage:
study.optimize(objective_single_dynamic, n_trials=50)
# Single objective study with 'inf' value
study = optuna.create_study(study_name="single-inf", storage=storage)
def objective_single_inf(trial: optuna.Trial) -> float:
x = trial.suggest_float("x", -10, 10)
if x > 0:
return math.inf
else:
return x ** 2
study.optimize(objective_single_inf, n_trials=50)
# Multi-objective study
study = optuna.create_study(
study_name="multi-objective",