Skip to content

Commit 50ce038

Browse files
vizier-teamcopybara-github
authored andcommitted
Implements metric normalization in the HV Curve Converter
PiperOrigin-RevId: 720664003
1 parent 0ba8ae2 commit 50ce038

3 files changed

Lines changed: 56 additions & 36 deletions

File tree

vizier/_src/algorithms/ensemble/ensemble_designer.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -74,6 +74,7 @@ def curve_generator() -> analyzers.StatefulCurveConverter:
7474
reference_value=self.reference_value,
7575
num_vectors=self.num_vectors,
7676
infer_origin_factor=0.1,
77+
disable_metric_normalization=True,
7778
)
7879

7980
stateful_curve_generator = analyzers.RestartingCurveConverter(

vizier/_src/benchmarks/analyzers/convergence_curve.py

Lines changed: 28 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -349,6 +349,7 @@ def __init__(
349349
reference_value: Optional[np.ndarray] = None,
350350
num_vectors: int = 10000,
351351
infer_origin_factor: float = 0.0,
352+
disable_metric_normalization: bool = False,
352353
):
353354
"""Init.
354355
@@ -361,6 +362,7 @@ def __init__(
361362
num_vectors: Number of vectors from which hypervolume is computed.
362363
infer_origin_factor: When inferring the reference point, set origin to be
363364
minimum value - factor * (range).
365+
disable_metric_normalization: If True, disable metric normalization.
364366
"""
365367
if len(metric_informations) < 2:
366368
raise ValueError(
@@ -386,8 +388,9 @@ def create_metric_converter(mc):
386388
self._origin_value = reference_value
387389
# TODO: Speed this up with hypervolume vector tracking.
388390
self._min_trial_idx = 1
389-
self._pareto_frontier = np.empty(shape=(0, len(metric_informations)))
390391
self._infer_origin_factor = infer_origin_factor
392+
self._disable_metric_normalization = disable_metric_normalization
393+
self._all_metrics = np.empty(shape=(0, len(metric_informations)))
391394

392395
def convert(self, trials: Sequence[pyvizier.Trial]) -> ConvergenceCurve:
393396
"""Returns ConvergenceCurve with a curve of shape 1 x len(trials)."""
@@ -396,6 +399,28 @@ def convert(self, trials: Sequence[pyvizier.Trial]) -> ConvergenceCurve:
396399
raise ValueError(f'No trials provided {trials}')
397400

398401
metrics = self._converter.to_labels_array(trials)
402+
self._all_metrics = np.vstack([self._all_metrics, metrics])
403+
if self._disable_metric_normalization:
404+
normalizer = 1.0
405+
else:
406+
normalizer = np.nanmedian(
407+
np.absolute(
408+
self._all_metrics
409+
- np.nanmedian(self._all_metrics, axis=0, keepdims=True)
410+
),
411+
axis=0,
412+
keepdims=True,
413+
)
414+
if np.any(normalizer <= 0):
415+
raise ValueError(
416+
'Some metric normalizers are zero. This is likely due to some'
417+
' metrics being identical or contain a lot of duplicates across'
418+
f' trials. Normalizers: {normalizer}'
419+
)
420+
metrics = metrics / normalizer
421+
# shape is [num_existing_points + num_new_points, num_metrics]
422+
all_metrics = self._all_metrics / normalizer
423+
399424
if self._origin_value is None:
400425
# Set origin to the minimum of finite values.
401426
origin = np.zeros(shape=(metrics.shape[1],))
@@ -428,10 +453,6 @@ def convert(self, trials: Sequence[pyvizier.Trial]) -> ConvergenceCurve:
428453
)
429454
origin = self._origin_value
430455

431-
# Calculate cumulative hypervolume with the Pareto frontier.
432-
all_metrics = np.vstack(
433-
[self._pareto_frontier, metrics]
434-
) # shape is [num_pareto_points + num_points, feature dimension]
435456
front = multimetric.ParetoFrontier(
436457
points=all_metrics,
437458
origin=origin,
@@ -440,17 +461,12 @@ def convert(self, trials: Sequence[pyvizier.Trial]) -> ConvergenceCurve:
440461
)
441462
all_hv_curve = front.hypervolume(is_cumulative=True)
442463

443-
# Remove the Pareto frontier add-in and update state.
444-
hv_curve = all_hv_curve[len(self._pareto_frontier) :]
464+
# Extracts the hv points for the new trials.
465+
hv_curve = all_hv_curve[-metrics.shape[0] :]
445466
xs = np.asarray(
446467
range(self._min_trial_idx, len(hv_curve) + self._min_trial_idx)
447468
)
448469
self._min_trial_idx += len(hv_curve)
449-
algo = multimetric.FastParetoOptimalAlgorithm(
450-
xla_pareto.JaxParetoOptimalAlgorithm()
451-
)
452-
pareto_points = algo.is_pareto_optimal(points=all_metrics)
453-
self._pareto_frontier = all_metrics[pareto_points]
454470

455471
return ConvergenceCurve(
456472
xs=xs,

vizier/_src/benchmarks/analyzers/convergence_curve_test.py

Lines changed: 27 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -278,25 +278,28 @@ def test_convert_with_origin_reference(self):
278278
pytrials = []
279279
pytrials.append(
280280
pyvizier.Trial().complete(
281-
pyvizier.Measurement(metrics={'max': 4.0, 'min': 2.0})
281+
pyvizier.Measurement(metrics={'max': 8.0, 'min': 0.0})
282282
)
283283
)
284284
pytrials.append(
285285
pyvizier.Trial().complete(
286-
pyvizier.Measurement(metrics={'max': 3.0, 'min': -1.0})
286+
pyvizier.Measurement(metrics={'max': 6.0, 'min': -2.0})
287287
)
288288
)
289289
pytrials.append(
290290
pyvizier.Trial().complete(
291-
pyvizier.Measurement(metrics={'max': 4.0, 'min': -2.0})
291+
pyvizier.Measurement(metrics={'max': 4.0, 'min': -4.0})
292292
)
293293
)
294294

295295
curve = generator.convert(pytrials)
296296
np.testing.assert_array_equal(curve.xs, [1, 2, 3])
297-
np.testing.assert_array_almost_equal(
298-
curve.ys, [[0.0, 3.0, 8.0]], decimal=0.5
299-
)
297+
# After the sign of the minimization metric is flipped, the three metric
298+
# points are (8, 0), (6, 2), (4, 4). After normalization, they become
299+
# (4, 0), (3, 1), (2, 2). The origin is (0, 0). The sequence of hypervolume
300+
# is expected to be (0, 3.5, 6.0). However, we allow a large error because
301+
# the hypervolume computation is approximate.
302+
np.testing.assert_array_almost_equal(curve.ys, [[0.0, 3.5, 6.0]], decimal=0)
300303

301304
def test_convert_with_reference(self):
302305
generator = convergence.HypervolumeCurveConverter(
@@ -313,25 +316,29 @@ def test_convert_with_reference(self):
313316
pytrials = []
314317
pytrials.append(
315318
pyvizier.Trial().complete(
316-
pyvizier.Measurement(metrics={'max': 4.0, 'min': 2.0})
319+
pyvizier.Measurement(metrics={'max': 8.0, 'min': 0.0})
317320
)
318321
)
319322
pytrials.append(
320323
pyvizier.Trial().complete(
321-
pyvizier.Measurement(metrics={'max': 3.0, 'min': -1.0})
324+
pyvizier.Measurement(metrics={'max': 6.0, 'min': -2.0})
322325
)
323326
)
324327
pytrials.append(
325328
pyvizier.Trial().complete(
326-
pyvizier.Measurement(metrics={'max': 4.0, 'min': -2.0})
329+
pyvizier.Measurement(metrics={'max': 4.0, 'min': -4.0})
327330
)
328331
)
329332

330333
curve = generator.convert(pytrials)
331334
np.testing.assert_array_equal(curve.xs, [1, 2, 3])
332-
np.testing.assert_array_almost_equal(
333-
curve.ys, [[0.0, 0.0, 2.0]], decimal=0.5
334-
)
335+
# After the sign of the minimization metric is flipped, the three metric
336+
# points are (8, 0), (6, 2), (4, 4). After normalization, they become
337+
# (4, 0), (3, 1), (2, 2). After accounting for the reference point (3, 0),
338+
# they are (1, 0), (0, 1), (-1, 2). The sequence of hypervolume
339+
# is expected to be (0, 0.5, 0.5). However, we allow a large error
340+
# because the hypervolume computation is approximate.
341+
np.testing.assert_array_almost_equal(curve.ys, [[0.0, 0.5, 0.5]], decimal=0)
335342

336343
def test_convert_with_none_reference(self):
337344
generator = convergence.HypervolumeCurveConverter([
@@ -361,9 +368,7 @@ def test_convert_with_none_reference(self):
361368

362369
curve = generator.convert(pytrials)
363370
np.testing.assert_array_equal(curve.xs, [1, 2, 3])
364-
np.testing.assert_array_almost_equal(
365-
curve.ys, [[0.0, 0.0, 1.0]], decimal=0.5
366-
)
371+
np.testing.assert_array_almost_equal(curve.ys, [[0.0, 0.0, 1.0]], decimal=0)
367372

368373
def test_convert_with_inf_none_reference(self):
369374
generator = convergence.HypervolumeCurveConverter([
@@ -401,6 +406,7 @@ def test_convert_with_state(self):
401406
),
402407
],
403408
reference_value=np.array([0.0]),
409+
disable_metric_normalization=True,
404410
)
405411
pytrials = []
406412
pytrials.append(
@@ -415,15 +421,13 @@ def test_convert_with_state(self):
415421
)
416422
pytrials.append(
417423
pyvizier.Trial().complete(
418-
pyvizier.Measurement(metrics={'max': 4.0, 'min': -2.0})
424+
pyvizier.Measurement(metrics={'max': 3.0, 'min': -2.0})
419425
)
420426
)
421427

422428
curve = generator.convert(pytrials)
423429
np.testing.assert_array_equal(curve.xs, [1, 2, 3])
424-
np.testing.assert_array_almost_equal(
425-
curve.ys, [[0.0, 5.0, 9.0]], decimal=0.5
426-
)
430+
np.testing.assert_array_almost_equal(curve.ys, [[0.0, 5.0, 9.0]], decimal=0)
427431

428432
pytrials = []
429433
pytrials.append(
@@ -439,7 +443,7 @@ def test_convert_with_state(self):
439443

440444
curve = generator.convert(pytrials)
441445
np.testing.assert_array_equal(curve.xs, [4, 5])
442-
np.testing.assert_array_almost_equal(curve.ys, [[9.0, 10.0]], decimal=0.5)
446+
np.testing.assert_array_almost_equal(curve.ys, [[9.0, 10.0]], decimal=0)
443447

444448
def test_convert_factor_with_inf(self):
445449
generator = convergence.HypervolumeCurveConverter(
@@ -534,7 +538,7 @@ def test_convert_multiobjective(self):
534538
pytrials = []
535539
pytrials.append(
536540
pyvizier.Trial().complete(
537-
pyvizier.Measurement(metrics={'max': 4.0, 'min': -1.0, 'safe': 1.0})
541+
pyvizier.Measurement(metrics={'max': 2.0, 'min': 0.0, 'safe': 1.0})
538542
)
539543
)
540544
pytrials.append(
@@ -552,9 +556,7 @@ def test_convert_multiobjective(self):
552556

553557
curve = generator.convert(pytrials)
554558
np.testing.assert_array_equal(curve.xs, [1, 2, 3])
555-
np.testing.assert_array_almost_equal(
556-
curve.ys, [[4.0, 4.0, 8.0]], decimal=0.5
557-
)
559+
np.testing.assert_array_almost_equal(curve.ys, [[0.0, 0.0, 2.0]], decimal=0)
558560

559561

560562
class RestartingCurveConverterTest(absltest.TestCase):
@@ -570,6 +572,7 @@ def converter_factory():
570572
name='min', goal=pyvizier.ObjectiveMetricGoal.MINIMIZE
571573
),
572574
],
575+
disable_metric_normalization=True,
573576
)
574577

575578
restart_converter = convergence.RestartingCurveConverter(

0 commit comments

Comments
 (0)