8000 1, Finish NormalizingExperimenter over all flat search spaces by using converters instead. by copybara-service[bot] · Pull Request #1161 · google/vizier · GitHub
[go: up one dir, main page]

Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
78 changes: 57 additions & 21 deletions vizier/_src/benchmarks/experimenters/normalizing_experimenter.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,11 +15,12 @@
from __future__ import annotations

"""Experimenter that normalizes the range of each metric."""

import copy
from typing import Dict, Sequence

import numpy as np
from vizier import pyvizier
from vizier import pyvizier as vz
from vizier._src.benchmarks.experimenters import experimenter
from vizier.pyvizier import converters

Expand All @@ -42,25 +43,19 @@ def __init__(
self._exptr = exptr
self._problem_statement = exptr.problem_statement()

min_vals: list[float] = []
max_vals: list[float] = []
for pc in self._problem_statement.search_space.parameters:
if pc.type != pyvizier.ParameterType.DOUBLE:
raise ValueError(f'Only DOUBLE parameters can be normalized: {pc}.')
min_value, max_value = pc.bounds
min_vals.append(min_value)
max_vals.append(max_value)

grid = np.linspace(
np.asarray(min_vals), np.asarray(max_vals), num_normalization_samples
)
# Convert search space into hypercube, then sample.
converter = converters.TrialToArrayConverter.from_study_config(
study_config=self._problem_statement, scale=False
study_config=self._problem_statement, scale=True, pad_oovs=False
)
grid_parameters = converter.to_parameters(grid)
feature_dim = sum([o.num_dimensions for o in converter.output_specs])

normalized_samples = np.linspace(
np.zeros(feature_dim), np.ones(feature_dim), num_normalization_samples
)
sampled_params = converter.to_parameters(normalized_samples)
metrics: Dict[str, list[float]] = {}
for parameters in grid_parameters:
trial = pyvizier.Trial(parameters=parameters)
for parameters in sampled_params:
trial = vz.Trial(parameters=parameters)
exptr.evaluate([trial])
measurement = trial.final_measurement
for name, metric in (measurement.metrics if measurement else {}).items():
Expand All @@ -79,17 +74,17 @@ def __init__(
)
self._normalizations[name] = normalization

def problem_statement(self) -> pyvizier.ProblemStatement:
def problem_statement(self) -> vz.ProblemStatement:
return copy.deepcopy(self._problem_statement)

def evaluate(self, suggestions: Sequence[pyvizier.Trial]):
def evaluate(self, suggestions: Sequence[vz.Trial]):
self._exptr.evaluate(suggestions)
for suggestion in suggestions:
if suggestion.final_measurement is None:
continue
normalized_metrics: Dict[str, pyvizier.Metric] = {}
normalized_metrics: Dict[str, vz.Metric] = {}
for name, metric in suggestion.final_measurement.metrics.items():
normalized_metrics[name] = pyvizier.Metric(
normalized_metrics[name] = vz.Metric(
value=metric.value / self._normalizations[name]
)
suggestion.final_measurement.metrics = normalized_metrics
Expand All @@ -99,3 +94,44 @@ def __repr__(self):
f'NormalizingExperimenter with normalizations {self._normalizations} on'
f' {self._exptr}'
)


class HyperCubeExperimenter(experimenter.Experimenter):
"""Normalizes the search space into unit hypercube using converter."""

def __init__(self, exptr: experimenter.Experimenter):
self._exptr = exptr
original_problem = exptr.problem_statement()

converter = converters.TrialToArrayConverter.from_study_config(
study_config=original_problem, scale=True, pad_oovs=False
)
self._converter = converter
feature_dim = sum([o.num_dimensions for o in converter.output_specs])

new_space = vz.SearchSpace() # Setup hypercube search space.
for i in range(feature_dim):
new_space.add(vz.ParameterConfig.factory(f'h{i}', bounds=(0.0, 1.0)))
self._problem_statement = copy.deepcopy(original_problem)
self._problem_statement.search_space = new_space

# For simply converting new hypercube parameters to numpy arrays.
self._eval_converter = converters.TrialToArrayConverter.from_study_config(
study_config=self._problem_statement, scale=False, pad_oovs=False
)

def problem_statement(self) -> vz.ProblemStatement:
return copy.deepcopy(self._problem_statement)

def evaluate(self, suggestions: Sequence[vz.Trial]):
hypercube_features = self._eval_converter.to_features(suggestions)
original_params = self._converter.to_parameters(hypercube_features)

orig_suggestions = copy.deepcopy(suggestions)
for param_dict, orig_suggestion in zip(original_params, orig_suggestions):
orig_suggestion.parameters = param_dict

self._exptr.evaluate(orig_suggestions)

for suggestion, orig_suggestion in zip(suggestions, orig_suggestions):
suggestion.final_measurement = orig_suggestion.final_measurement
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
from __future__ import annotations

from vizier import pyvizier
from vizier._src.benchmarks.experimenters import combo_experimenter
from vizier._src.benchmarks.experimenters import normalizing_experimenter
from vizier._src.benchmarks.experimenters import numpy_experimenter
from vizier._src.benchmarks.experimenters.synthetic import bbob
Expand Down Expand Up @@ -72,5 +73,46 @@ def testNormalizationApply(self, func):
self.assertBetween(normalized_value, -10, 10)


class HyperCubeExperimenterTest(parameterized.TestCase):

def testE2E(self):
num_boolean_params = 3

original_exptr = combo_experimenter.ContaminationExperimenter(
contamination_n_stages=num_boolean_params
)
hypercube_exptr = normalizing_experimenter.HyperCubeExperimenter(
original_exptr
)

problem = hypercube_exptr.problem_statement()
metric_name = problem.metric_information.item().name

# 3 Booleans -> 6 total coordinates after one-hots.
self.assertLen(problem.search_space.parameters, 2 * num_boolean_params)

t = pyvizier.Trial(
parameters={
'h0': 0.0,
'h1': 1.0,
'h2': 1.0,
'h3': 0.0,
'h4': 0.3,
'h5': 0.6,
}
)
hypercube_exptr.evaluate([t])
hypercube_value = t.final_measurement_or_die.metrics[metric_name].value

# Argmax over hypercube one-hots.
t = pyvizier.Trial(
parameters={'x_0': 'True', 'x_1': 'False', 'x_2': 'True'}
)
original_exptr.evaluate([t])
original_value = t.final_measurement_or_die.metrics[metric_name].value

self.assertEqual(hypercube_value, original_value)


if __name__ == '__main__':
absltest.main()
1 change: 1 addition & 0 deletions vizier/benchmarks/experimenters/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@
from vizier._src.benchmarks.experimenters.l1_categorical_experimenter import L1CategorialExperimenter
from vizier._src.benchmarks.experimenters.multiobjective_experimenter import MultiObjectiveExperimenter
from vizier._src.benchmarks.experimenters.noisy_experimenter import NoisyExperimenter
from vizier._src.benchmarks.experimenters.normalizing_experimenter import HyperCubeExperimenter
from vizier._src.benchmarks.experimenters.normalizing_experimenter import NormalizingExperimenter
from vizier._src.benchmarks.experimenters.numpy_experimenter import MultiObjectiveNumpyExperimenter
from vizier._src.benchmarks.experimenters.numpy_experimenter import NumpyExperimenter
Expand Down
0