-
-
Notifications
You must be signed in to change notification settings - Fork 95
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Fast pareto-front calculation for 2D #687
Changes from 7 commits
bd69232
e3c1d74
aba648e
becaff1
4ef4aa1
b0f66b3
5b4f0bb
8ebed9b
422ffd2
01ecc55
a9b23ae
29d44ab
4315c9d
1203501
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change | ||||
---|---|---|---|---|---|---|
|
@@ -165,6 +165,58 @@ const makeMarker = ( | |||||
} | ||||||
} | ||||||
|
||||||
const getIsDominatedTrialND = (normalizedValues: number[][]) => { | ||||||
// Fallback for straight-forward pareto front algorithm (O(N^2) complexity). | ||||||
const dominatedTrials: boolean[] = [] | ||||||
normalizedValues.forEach((values0: number[]) => { | ||||||
const dominated = normalizedValues.some((values1: number[]) => { | ||||||
if (values0.every((value0: number, k: number) => values1[k] === value0)) { | ||||||
return false | ||||||
} | ||||||
return values0.every((value0: number, k: number) => values1[k] <= value0) | ||||||
}) | ||||||
dominatedTrials.push(dominated) | ||||||
}) | ||||||
return dominatedTrials | ||||||
} | ||||||
|
||||||
const getIsDominatedTrial2D = (normalizedValues: number[][]) => { | ||||||
// Fast pareto front algorithm (O(N log N) complexity). | ||||||
const sorted = normalizedValues | ||||||
.map((values, i) => [values[0], values[1], i]) | ||||||
.sort() | ||||||
let minValue1 = sorted[0][1] | ||||||
const dominatedTrials: boolean[] = new Array(normalizedValues.length).fill( | ||||||
true | ||||||
) | ||||||
|
||||||
sorted.forEach((values) => { | ||||||
if (values[1] <= minValue1) { | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This condition is buggy when the values between trials are the same. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. In that case, both trials should be considered undominated. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Now given a sequence of 2D vectors For example, Btw, I guessed you are trying to get "weak dominance" from Maybe I am wrong, if so, correct me please! const normalizedValues = [[0, 1], [1, 1], [2, 1]]
const getIsDominatedTrial2D = (normalizedValues: number[][]) => {
const sortedValues = normalizedValues
.map((values, i) => [values[0], values[1], i])
.sort((a, b) => a[0] !== b[0] ? a[0] - b[0] : a[1] - b[1])
let minValue1 = sortedValues[0][1]
const dominatedTrials: boolean[] = new Array(normalizedValues.length).fill(
true
)
sortedValues.forEach((values) => {
if (values[1] <= minValue1) {
dominatedTrials[values[2]] = false
minValue1 = values[1]
}
})
return dominatedTrials
}
console.log(getIsDominatedTrial2D(normalizedValues)) Output
I guess (if we need weakly dominated trials) what we want is:
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Quick solution to this is the following: const normalizedValues = [[0, 1], [1, 1], [2, 1]]
const getIsDominatedTrial2D = (normalizedValues: number[][]) => {
const sortedValues = normalizedValues
.map((values, i) => [values[0], values[1], i])
.sort((a, b) => a[0] !== b[0] ? a[0] - b[0] : a[1] - b[1])
let minValue1 = sortedValues[0][1]
let latestParetoSolution = sortedValues[0].slice(0, 2)
const dominatedTrials: boolean[] = new Array(normalizedValues.length).fill(
true
)
sortedValues.forEach((values) => {
if (values[1] < minValue1 || latestParetoSolution.every((v, i) => v === values[i])) {
dominatedTrials[values[2]] = false
minValue1 = values[1]
latestParetoSolution = values.slice(0, 2)
}
})
return dominatedTrials
}
console.log(getIsDominatedTrial2D(normalizedValues)) There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. See https://github.com/optuna/optuna/blob/master/optuna/study/_multi_objective.py#L101. Optuna uses There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Thank you for pointing out. My statement is incorrect and @nabenabe0928's one is correct, I think. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The new modification fails with the following case.
We get:
But we would like to have:
I think the suggestion here is sufficient. |
||||||
dominatedTrials[values[2]] = false | ||||||
minValue1 = values[1] | ||||||
} | ||||||
}) | ||||||
return dominatedTrials | ||||||
} | ||||||
|
||||||
const getIsDominatedTrial1D = (normalizedValues: number[][]) => { | ||||||
const best_value = Math.min(...normalizedValues.map((values) => values[0])) | ||||||
return normalizedValues.map((value) => value[0] !== best_value) | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
|
||||||
} | ||||||
|
||||||
const getIsDominatedTrial = (normalizedValues: number[][]) => { | ||||||
if (normalizedValues.length === 0) { | ||||||
return [] | ||||||
} | ||||||
if (normalizedValues[0].length === 1) { | ||||||
return getIsDominatedTrial1D(normalizedValues) | ||||||
} else if (normalizedValues[0].length === 2) { | ||||||
return getIsDominatedTrial2D(normalizedValues) | ||||||
} else { | ||||||
return getIsDominatedTrialND(normalizedValues) | ||||||
} | ||||||
} | ||||||
|
||||||
const plotParetoFront = ( | ||||||
study: StudyDetail, | ||||||
objectiveXId: number, | ||||||
|
@@ -218,18 +270,7 @@ const plotParetoFront = ( | |||||
} | ||||||
}) | ||||||
|
||||||
const dominatedTrials: boolean[] = [] | ||||||
normalizedValues.forEach((values0: number[], i: number) => { | ||||||
const dominated = normalizedValues.some((values1: number[], j: number) => { | ||||||
if (i === j) { | ||||||
return false | ||||||
} | ||||||
return values0.every((value0: number, k: number) => { | ||||||
return values1[k] <= value0 | ||||||
}) | ||||||
}) | ||||||
dominatedTrials.push(dominated) | ||||||
}) | ||||||
const dominatedTrials: boolean[] = getIsDominatedTrial(normalizedValues) | ||||||
|
||||||
const plotData: Partial<plotly.PlotData>[] = [ | ||||||
makeScatterObject( | ||||||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Output
So you still need to change the comparator inside
sort()
.Output after the change: