Skip to content

Commit ec6287b

Browse files
vizier-teamcopybara-github
authored andcommitted
Allow treating discrete/integer parameters with many feasible values as continuous for trust regions.
PiperOrigin-RevId: 799598361
1 parent 995c117 commit ec6287b

5 files changed

Lines changed: 75 additions & 23 deletions

File tree

‎vizier/_src/algorithms/designers/gp_bandit.py‎

Lines changed: 13 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,13 @@
5050
from vizier.pyvizier.converters import padding
5151
from vizier.utils import profiler
5252

53+
54+
# The maximum number of feasible values to use for the trust region. If a
55+
# discrete or integer parameter has more than this many feasible values, it is
56+
# considered continuous and always included in the trust region computation.
57+
_MAX_NUM_FEASIBLE_VALUES_FOR_TRUST_REGION = 1000
58+
59+
5360
default_acquisition_optimizer_factory = vb.VectorizedOptimizerFactory(
5461
strategy_factory=es.VectorizedEagleStrategyFactory(
5562
eagle_config=es.EagleStrategyConfig()
@@ -246,7 +253,9 @@ def acq_fn_factory(data: types.ModelData) -> acq_lib.AcquisitionFunction:
246253
scoring_fn = self._scoring_function_factory(
247254
empty_data,
248255
predictive,
249-
self._converter.continuous_feasible_values,
256+
self._converter.continuous_feasible_values(
257+
max_num_feasible_values=_MAX_NUM_FEASIBLE_VALUES_FOR_TRUST_REGION
258+
),
250259
self._use_trust_region,
251260
)
252261
if (
@@ -532,7 +541,9 @@ def suggest(self, count: int = 1) -> Sequence[vz.TrialSuggestion]:
532541
scoring_fn = self._scoring_function_factory(
533542
data,
534543
gp,
535-
self._converter.continuous_feasible_values,
544+
self._converter.continuous_feasible_values(
545+
max_num_feasible_values=_MAX_NUM_FEASIBLE_VALUES_FOR_TRUST_REGION
546+
),
536547
self._use_trust_region,
537548
)
538549
logging.info('Optimizing acquisition: %s', scoring_fn)

‎vizier/_src/algorithms/designers/gp_ucb_pe.py‎

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,12 @@
5454
tfd = tfp.distributions
5555

5656

57+
# The maximum number of feasible values to use for the trust region. If a
58+
# discrete or integer parameter has more than this many feasible values, it is
59+
# considered continuous and always included in the trust region computation.
60+
_MAX_NUM_FEASIBLE_VALUES_FOR_TRUST_REGION = 1000
61+
62+
5763
class MultimetricPromisingRegionPenaltyType(enum.Enum):
5864
"""The type of penalty to apply to the points outside the promising region.
5965
@@ -1390,7 +1396,9 @@ def suggest(
13901396
)
13911397
tr = acquisitions.TrustRegion(
13921398
trusted=tr_features,
1393-
continuous_feasible_values=self._converter.continuous_feasible_values,
1399+
continuous_feasible_values=self._converter.continuous_feasible_values(
1400+
max_num_feasible_values=_MAX_NUM_FEASIBLE_VALUES_FOR_TRUST_REGION
1401+
),
13941402
)
13951403

13961404
acquisition_problem = copy.deepcopy(self._problem)

‎vizier/_src/algorithms/designers/gp_ucb_pe_test.py‎

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -615,6 +615,10 @@ def test_discrete_parameters_are_explored(
615615
root.add_discrete_param('discrete_double_1', [-3.0, 7.0])
616616
root.add_discrete_param('discrete_double_single_feasible_value', [0.37])
617617
root.add_float_param('double', min_value=-5.0, max_value=5.0)
618+
root.add_int_param('integer_with_many_feasible_values', -1, 6 * 10**7)
619+
root.add_discrete_param(
620+
'discrete_double_many_feasible_values', np.linspace(-10.0, 10.0, 1000)
621+
)
618622
problem = vz.ProblemStatement(space)
619623
problem.metric_information.append(
620624
vz.MetricInformation(

‎vizier/pyvizier/converters/jnp_converters.py‎

Lines changed: 36 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -246,29 +246,52 @@ def output_specs(self): # TODO: Add back pytype
246246
def metric_specs(self) -> Sequence[vz.MetricInformation]:
247247
return self._impl.metric_specs
248248

249-
@property
250-
def continuous_feasible_values(self) -> list[jax.Array]:
249+
def continuous_feasible_values(
250+
self, max_num_feasible_values: int | None = None
251+
) -> list[jax.Array]:
251252
"""Returns a list of feasible values for each continuified parameter.
252253
253254
The list is ordered the same as the parameters in the search space. An empty
254255
array means that the parameter is continuous and all values within its
255256
range are feasible. The returned feasible values are in the scaled space.
256-
Categorical parameters are ignored.
257+
Categorical parameters are ignored. If `max_num_feasible_values` is
258+
specified, discrete or integer parameters with more than
259+
`max_num_feasible_values` feasible values are considered continuous and
260+
empty arrays are returned for them.
261+
262+
Args:
263+
max_num_feasible_values: If specified, discrete or integer parameters with
264+
more than this many feasible values are considered continuous and empty
265+
arrays are returned for them.
266+
267+
Returns:
268+
A list of feasible values for each continuified parameter. The list is
269+
ordered the same as the parameters in the search space.
257270
"""
258271

259272
continuous_feasible_values = []
260273
for param in self._problem.search_space.parameters:
261274
if param.type in [vz.ParameterType.DISCRETE, vz.ParameterType.INTEGER]:
262-
converted_feasible_values = self.to_features([
263-
vz.TrialSuggestion(parameters={param.name: value})
264-
for value in param.feasible_values
265-
]).continuous.unpad()
266-
continuous_feasible_values.append(
267-
# `self.to_features` returns NaNs for missing parameters (we call it
268-
# with one specific parameter instead of all parameters in the
269-
# search space), so we remove them here.
270-
converted_feasible_values[~jnp.isnan(converted_feasible_values)]
271-
)
275+
if param.type == vz.ParameterType.INTEGER:
276+
num_feasible_values = param.bounds[1] - param.bounds[0] + 1
277+
else:
278+
num_feasible_values = len(param.feasible_values)
279+
if (
280+
max_num_feasible_values is not None
281+
and num_feasible_values > max_num_feasible_values
282+
):
283+
continuous_feasible_values.append(jnp.asarray([]))
284+
else:
285+
converted_feasible_values = self.to_features([
286+
vz.TrialSuggestion(parameters={param.name: value})
287+
for value in param.feasible_values
288+
]).continuous.unpad()
289+
continuous_feasible_values.append(
290+
# `self.to_features` returns NaNs for missing parameters (we call
291+
# it with one specific parameter instead of all parameters in the
292+
# search space), so we remove them here.
293+
converted_feasible_values[~jnp.isnan(converted_feasible_values)]
294+
)
272295
elif param.type == vz.ParameterType.DOUBLE:
273296
continuous_feasible_values.append(jnp.asarray([]))
274297
elif param.type == vz.ParameterType.CUSTOM:

‎vizier/pyvizier/converters/jnp_converters_test.py‎

Lines changed: 13 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -110,7 +110,7 @@ def setUp(self):
110110
self.maxDiff = None
111111

112112
def test_continuous_feasible_values(self):
113-
"""Tests the `continuous_feasible_values` property.
113+
"""Tests the `continuous_feasible_values` method.
114114
115115
The returned feasible values are expected to be in the scaled space, and
116116
categorical parameters are ignored.
@@ -125,6 +125,7 @@ def test_continuous_feasible_values(self):
125125
)
126126
root.add_int_param('integer', -2, 2)
127127
root.add_float_param('float', -3, 4)
128+
root.add_int_param('integer_with_many_feasible_values', -1, 6 * 10**7)
128129
converter = jnpc.TrialToModelInputConverter.from_problem(
129130
vz.ProblemStatement(
130131
search_space=space,
@@ -135,19 +136,22 @@ def test_continuous_feasible_values(self):
135136
],
136137
)
137138
)
138-
self.assertLen(converter.continuous_feasible_values, 5)
139+
continuous_feasible_values = converter.continuous_feasible_values(
140+
max_num_feasible_values=1000
141+
)
142+
self.assertLen(continuous_feasible_values, 6)
139143
self.assertSequenceAlmostEqual(
140-
converter.continuous_feasible_values[0],
144+
continuous_feasible_values[0],
141145
# The original feasible values are linearly scaled to [0, 1].
142146
(np.asarray([-0.4, -0.3, 0.4, 1.1, 1.15]) + 0.4) / 1.55,
143147
)
144148
self.assertSequenceAlmostEqual(
145149
# The original feasible values [5, 9] are linearly scaled to [0, 1].
146-
converter.continuous_feasible_values[1],
150+
continuous_feasible_values[1],
147151
np.asarray([0.0, 1.0]),
148152
)
149153
self.assertSequenceAlmostEqual(
150-
converter.continuous_feasible_values[2],
154+
continuous_feasible_values[2],
151155
# The original feasible values [1e-5, 1e-2, 1e-1] are log-then-linearly
152156
# scaled to [0, 1].
153157
(
@@ -156,13 +160,15 @@ def test_continuous_feasible_values(self):
156160
),
157161
)
158162
self.assertSequenceAlmostEqual(
159-
converter.continuous_feasible_values[3],
163+
continuous_feasible_values[3],
160164
# The integer parameter with range [-2, 2] are internally scaled to
161165
# evenly spaced values in [0, 1].
162166
np.asarray([0.0, 0.25, 0.5, 0.75, 1.0]),
163167
)
164168
# Feasible values for float parameters are empty.
165-
self.assertEmpty(converter.continuous_feasible_values[4])
169+
self.assertEmpty(continuous_feasible_values[4])
170+
# Feasible values for int parameters with many feasible values are empty.
171+
self.assertEmpty(continuous_feasible_values[5])
166172

167173
def test_back_to_back_conversion(self):
168174
converter = jnpc.TrialToContinuousAndCategoricalConverter.from_study_config(

0 commit comments

Comments
 (0)