Creating Benchmarks
We provide a guide below on creating benchmarks, through the use of either:
Standard search space primitives.
Metadata for complex search spaces.
Installation and reference imports
!pip install google-vizier
import abc
import random
from typing import Sequence
from vizier import pyvizier as vz
from vizier.benchmarks import experimenters
Experimenters
The core base class of any objective function is the Experimenter
class, which simply contains a method to evaluate a Trial
and a ProblemStatement
to describe its search space and metrics. The exact entry into the class can be found here.
class Experimenter(metaclass=abc.ABCMeta):
"""Abstract base class for Experimenters."""
@abc.abstractmethod
def evaluate(self, suggestions: Sequence[vz.Trial]) -> None:
"""Evaluates and mutates the Trials in-place."""
@abc.abstractmethod
def problem_statement(self) -> vz.ProblemStatement:
"""The search configuration generated by this experimenter."""
Below is an example of a basic 1D objective function \(f(x) = x^{2}\).
class Basic1DExperimenter(experimenters.Experimenter):
def evaluate(self, suggestions: Sequence[vz.Trial]) -> None:
for suggestion in suggestions:
x = suggestion.parameters['x'].value
objective = x**2
measurement = pyvizier.Measurement(metrics={'obj': objective})
suggestion.complete(measurement)
def problem_statement(self) -> vz.ProblemStatement:
problem_statement = vz.ProblemStatement()
root = problem_statement.search_space.root
root.add_float_param(name='x', min_value=-1.0, max_value=1.0)
metric = vz.MetricInformation(name='obj', goal=vz.ObjectiveMetricGoal.MAXIMIZE)
problem_statement.metric_information.append(metric)
return problem_statement
We may thus evaluate a suggestion. Note that such suggestions are actually Trial
s, to allow maximum flexibility.
basic_experimenter = Basic1DExperimenter()
trial = vz.Trial()
trial.parameters['x'] = 0.1
basic_experimenter.evaluate([trial])
assert trial.final_measurement.metrics['obj'].value == 0.1 ** 2
Metadata-based Experimenters
Similar to using the Metadata
primitive to create custom algorithms and
complex search spaces, creating custom Experimenter
s provides the freedom to
define custom objective functions.
As an example, suppose our search space consisted of unbounded-length sequences consisting of some vocabulary (e.g. the letters ‘A’ to ‘Z’ if considering the space of English words), and we wish to maximize the sequence’s average ASCII value.
class VocabularyExperimenter(experimenters.Experimenter):
def evaluate(self, suggestions: Sequence[vz.Trial]):
for suggestion in suggestions:
x = suggestion.metadata['word']
objective = float(sum([ord(c) for c in x])) / len(x)
measurement = vz.Measurement(metrics={'obj': objective})
suggestion.complete(measurement)
def problem_statement(self) -> pyvizier.ProblemStatement:
problem_statement = vz.ProblemStatement()
problem_statement.metadata['vocab'] = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ'
metric = vz.MetricInformation(name='obj', goal=vz.ObjectiveMetricGoal.MAXIMIZE)
problem_statement.metric_information.append(metric)
return problem_statement
Below is an example of constructing a valid suggestion and evaluating it.
vocab_experimenter = VocabularyExperimenter()
vocabulary = vocab_experimenter.problem_statement().metadata['vocab']
trial = vz.Trial()
trial.metadata['word'] = str(
[random.randint(0, len(vocabulary)) for _ in range(10)]
)
vocab_experimenter.evaluate([trial])
print('Average ASCII value is:', trial.final_measurement.metrics['obj'].value)