mirror of
https://github.com/wassname/optuna-dashboard.git
synced 2026-09-11 12:30:25 +08:00
Limit the maximum number of trials to be used for one update
More specifically, this PR makes the following changes: 1. Add query param for limit, 2. Update studyDetails only if there is no study with the specified study_id or the fetched trials has a positive length, 3. Shorten the waiting interval when there is a leftover in the server side, and 4. Adapt the flake8 setup to the Optuna repo.
This commit is contained in:
@@ -191,14 +191,19 @@ def create_app(
|
||||
@app.get("/api/studies/<study_id:int>")
|
||||
@json_api_view
|
||||
def get_study_detail(study_id: int) -> dict[str, Any]:
|
||||
try:
|
||||
after = int(request.params["after"])
|
||||
assert after >= 0
|
||||
except AssertionError:
|
||||
response.status = 400 # Bad parameter
|
||||
return {"reason": "`after` should be larger or equal 0."}
|
||||
except KeyError:
|
||||
after = 0
|
||||
query_params = dict(after=0, limit=1000)
|
||||
for query_key in query_params:
|
||||
try:
|
||||
query_params[query_key] = int(request.params[query_key])
|
||||
assert query_params[query_key] >= 0
|
||||
except AssertionError:
|
||||
response.status = 400 # Bad parameter
|
||||
return {"reason": f"`{query_key}` should be larger than or equal to 0."}
|
||||
except KeyError:
|
||||
# Use the default parameter defined in query_params.
|
||||
pass
|
||||
|
||||
after, limit = query_params["after"], query_params["limit"]
|
||||
summary = get_study_summary(storage, study_id)
|
||||
if summary is None:
|
||||
response.status = 404 # Not found
|
||||
@@ -229,16 +234,18 @@ def create_app(
|
||||
plotly_graph_objects = get_plotly_graph_objects(system_attrs)
|
||||
skipped_trial_ids = get_skipped_trial_ids(system_attrs)
|
||||
skipped_trial_numbers = [t.number for t in trials if t._trial_id in skipped_trial_ids]
|
||||
fetched_trials_partially = after + limit < len(trials)
|
||||
return serialize_study_detail(
|
||||
summary,
|
||||
best_trials,
|
||||
trials[after:],
|
||||
trials[after : after + limit],
|
||||
intersection,
|
||||
union,
|
||||
union_user_attrs,
|
||||
has_intermediate_values,
|
||||
plotly_graph_objects,
|
||||
skipped_trial_numbers,
|
||||
fetched_trials_partially,
|
||||
)
|
||||
|
||||
@app.get("/api/studies/<study_id:int>/param_importances")
|
||||
|
||||
@@ -141,11 +141,13 @@ def serialize_study_detail(
|
||||
has_intermediate_values: bool,
|
||||
plotly_graph_objects: dict[str, str],
|
||||
skipped_trial_numbers: list[int],
|
||||
fetched_trials_partially: bool,
|
||||
) -> dict[str, Any]:
|
||||
serialized: dict[str, Any] = {
|
||||
"name": summary.study_name,
|
||||
"directions": [d.name.lower() for d in summary.directions],
|
||||
"user_attrs": serialize_attrs(summary.user_attrs),
|
||||
"fetched_trials_partially": fetched_trials_partially,
|
||||
}
|
||||
system_attrs = getattr(summary, "system_attrs", {})
|
||||
serialized["artifacts"] = list_study_artifacts(system_attrs)
|
||||
|
||||
@@ -27,6 +27,7 @@ import {
|
||||
studySummariesState,
|
||||
paramImportanceState,
|
||||
isFileUploading,
|
||||
isTrialLeftInCache,
|
||||
artifactIsAvailable,
|
||||
reloadIntervalState,
|
||||
trialsUpdatingState,
|
||||
@@ -46,6 +47,7 @@ export const actionCreator = () => {
|
||||
const setUploading = useSetRecoilState<boolean>(isFileUploading)
|
||||
const setTrialsUpdating = useSetRecoilState(trialsUpdatingState)
|
||||
const setArtifactIsAvailable = useSetRecoilState<boolean>(artifactIsAvailable)
|
||||
const setIsTrialLeftInCache = useSetRecoilState<boolean>(isTrialLeftInCache)
|
||||
|
||||
const setStudyDetailState = (studyId: number, study: StudyDetail) => {
|
||||
setStudyDetails((prevVal) => {
|
||||
@@ -233,6 +235,7 @@ export const actionCreator = () => {
|
||||
|
||||
const updateStudyDetail = (studyId: number) => {
|
||||
let nLocalFixedTrials = 0
|
||||
let nMaximumTrialsAtOnce = 1000
|
||||
if (studyId in studyDetails) {
|
||||
const currentTrials = studyDetails[studyId].trials
|
||||
const firstUpdatable = currentTrials.findIndex((trial) =>
|
||||
@@ -240,15 +243,20 @@ export const actionCreator = () => {
|
||||
)
|
||||
nLocalFixedTrials =
|
||||
firstUpdatable === -1 ? currentTrials.length : firstUpdatable
|
||||
nMaximumTrialsAtOnce = 2000
|
||||
}
|
||||
getStudyDetailAPI(studyId, nLocalFixedTrials)
|
||||
getStudyDetailAPI(studyId, nLocalFixedTrials, nMaximumTrialsAtOnce)
|
||||
.then((study) => {
|
||||
const currentFixedTrials =
|
||||
studyId in studyDetails
|
||||
? studyDetails[studyId].trials.slice(0, nLocalFixedTrials)
|
||||
: []
|
||||
study.trials = currentFixedTrials.concat(study.trials)
|
||||
setStudyDetailState(studyId, study)
|
||||
if (study.trials.length !== 0 || !(studyId in studyDetails)) {
|
||||
// Update trials only if necessary. The second condition is for study with no trials.
|
||||
study.trials = currentFixedTrials.concat(study.trials)
|
||||
setStudyDetailState(studyId, study)
|
||||
setIsTrialLeftInCache(study.fetched_trials_partially)
|
||||
}
|
||||
})
|
||||
.catch((err) => {
|
||||
const reason = err.response?.data.reason
|
||||
|
||||
@@ -102,16 +102,19 @@ interface StudyDetailResponse {
|
||||
artifacts: Artifact[]
|
||||
feedback_component_type: FeedbackComponentType
|
||||
skipped_trial_numbers?: number[]
|
||||
fetched_trials_partially: boolean
|
||||
}
|
||||
|
||||
export const getStudyDetailAPI = (
|
||||
studyId: number,
|
||||
nLocalTrials: number
|
||||
nLocalTrials: number,
|
||||
nMaximumTrialsAtOnce: number
|
||||
): Promise<StudyDetail> => {
|
||||
return axiosInstance
|
||||
.get<StudyDetailResponse>(`/api/studies/${studyId}`, {
|
||||
params: {
|
||||
after: nLocalTrials,
|
||||
limit: nMaximumTrialsAtOnce,
|
||||
},
|
||||
})
|
||||
.then((res) => {
|
||||
@@ -145,6 +148,7 @@ export const getStudyDetailAPI = (
|
||||
plotly_graph_objects: res.data.plotly_graph_objects,
|
||||
artifacts: res.data.artifacts,
|
||||
skipped_trial_numbers: res.data.skipped_trial_numbers ?? [],
|
||||
fetched_trials_partially: res.data.fetched_trials_partially,
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
@@ -17,6 +17,7 @@ import DownloadIcon from "@mui/icons-material/Download"
|
||||
import { StudyNote } from "./Note"
|
||||
import { actionCreator } from "../action"
|
||||
import {
|
||||
isTrialLeftInCache,
|
||||
reloadIntervalState,
|
||||
useStudyDetailValue,
|
||||
useStudyIsPreferential,
|
||||
@@ -57,6 +58,7 @@ export const StudyDetail: FC<{
|
||||
const reloadInterval = useRecoilValue<number>(reloadIntervalState)
|
||||
const studyName = useStudyName(studyId)
|
||||
const isPreferential = useStudyIsPreferential(studyId)
|
||||
const isTrialLeft = useRecoilValue<boolean>(isTrialLeftInCache)
|
||||
|
||||
const title =
|
||||
studyName !== null ? `${studyName} (id=${studyId})` : `Study #${studyId}`
|
||||
@@ -73,9 +75,13 @@ export const StudyDetail: FC<{
|
||||
const nTrials = studyDetail ? studyDetail.trials.length : 0
|
||||
let interval = reloadInterval * 1000
|
||||
|
||||
// If trials are left in cache, we collect them quickly.
|
||||
// For Human-in-the-loop Optimization, the interval is set to 2 seconds
|
||||
// when the number of trials is small, and the page is "trialList" or top page of preferential.
|
||||
if (
|
||||
if (isTrialLeft) {
|
||||
// Too short time is frustrating because the page freezes until the rendering is done.
|
||||
interval = 3000
|
||||
} else if (
|
||||
(!isPreferential && page === "trialList") ||
|
||||
(isPreferential && page === "top")
|
||||
) {
|
||||
|
||||
@@ -33,6 +33,11 @@ export const drawerOpenState = atom<boolean>({
|
||||
default: false,
|
||||
})
|
||||
|
||||
export const isTrialLeftInCache = atom<boolean>({
|
||||
key: "isTrialLeftInCache",
|
||||
default: false,
|
||||
})
|
||||
|
||||
export const isFileUploading = atom<boolean>({
|
||||
key: "isFileUploading",
|
||||
default: false,
|
||||
|
||||
Vendored
+1
@@ -220,6 +220,7 @@ type StudyDetail = {
|
||||
plotly_graph_objects: PlotlyGraphObject[]
|
||||
artifacts: Artifact[]
|
||||
skipped_trial_numbers: number[]
|
||||
fetched_trials_partially: boolean
|
||||
}
|
||||
|
||||
type StudyDetails = {
|
||||
|
||||
@@ -61,7 +61,7 @@ def test_get_study_detail_is_preferential() -> None:
|
||||
|
||||
study_summary = study_summaries[0]
|
||||
study_detail = serialize_study_detail(
|
||||
study_summary, [], study.trials, [], [], [], False, {}, []
|
||||
study_summary, [], study.trials, [], [], [], False, {}, [], False
|
||||
)
|
||||
assert study_detail["is_preferential"]
|
||||
|
||||
@@ -74,7 +74,7 @@ def test_get_study_detail_is_not_preferential() -> None:
|
||||
|
||||
study_summary = study_summaries[0]
|
||||
study_detail = serialize_study_detail(
|
||||
study_summary, [], study.trials, [], [], [], False, {}, []
|
||||
study_summary, [], study.trials, [], [], [], False, {}, [], False
|
||||
)
|
||||
assert not study_detail["is_preferential"]
|
||||
|
||||
|
||||
Reference in New Issue
Block a user