Skip to content

Commit e5b1f5b

Browse files
VilockLifacebook-github-bot
authored andcommitted
Specify fidelity_parameters in Ax (#122)
Summary: Pull Request resolved: #122 Specify fidelity_dims (which is used by fidelity models) in `fit()` Put fidelity parameters to the last columns. Reviewed By: bletham Differential Revision: D16122299 fbshipit-source-id: a5d3139287e3b3afe87b81424b33a9dbe3ccd699
1 parent 37a0006 commit e5b1f5b

16 files changed

+97
-22
lines changed

ax/modelbridge/array.py

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,11 @@ def _fit(
4747
) -> None:
4848
# Convert observations to arrays
4949
self.parameters = list(search_space.parameters.keys())
50+
# move fidelity parameters to the last columns
51+
for para in search_space.parameters:
52+
if search_space.parameters[para].is_fidelity:
53+
self.parameters.remove(para)
54+
self.parameters.append(para)
5055
all_metric_names: Set[str] = set()
5156
for od in observation_data:
5257
all_metric_names.update(od.metric_names)
@@ -60,7 +65,9 @@ def _fit(
6065
)
6166
self.training_in_design = in_design
6267
# Extract bounds and task features
63-
bounds, task_features = get_bounds_and_task(search_space, self.parameters)
68+
bounds, task_features, fidelity_features = get_bounds_and_task(
69+
search_space, self.parameters
70+
)
6471

6572
# Fit
6673
self._model_fit(
@@ -71,6 +78,7 @@ def _fit(
7178
bounds=bounds,
7279
task_features=task_features,
7380
feature_names=self.parameters,
81+
fidelity_features=fidelity_features,
7482
)
7583

7684
def _model_fit(
@@ -82,6 +90,7 @@ def _model_fit(
8290
bounds: List[Tuple[float, float]],
8391
task_features: List[int],
8492
feature_names: List[str],
93+
fidelity_features: List[int],
8594
) -> None:
8695
"""Fit the model, given numpy types.
8796
"""
@@ -93,6 +102,7 @@ def _model_fit(
93102
bounds=bounds,
94103
task_features=task_features,
95104
feature_names=feature_names,
105+
fidelity_features=fidelity_features,
96106
)
97107

98108
def _update(
@@ -150,7 +160,7 @@ def _gen(
150160
if not self.parameters: # pragma: no cover
151161
raise ValueError(FIT_MODEL_ERROR.format(action="_gen"))
152162
# Extract bounds
153-
bounds, _ = get_bounds_and_task(search_space, self.parameters)
163+
bounds, _, _ = get_bounds_and_task(search_space, self.parameters)
154164
if optimization_config is None:
155165
raise ValueError(
156166
"ArrayModelBridge requires an OptimizationConfig to be specified"

ax/modelbridge/modelbridge_utils.py

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -31,12 +31,13 @@ def extract_parameter_constraints(
3131

3232
def get_bounds_and_task(
3333
search_space: SearchSpace, param_names: List[str]
34-
) -> Tuple[List[Tuple[float, float]], List[int]]:
34+
) -> Tuple[List[Tuple[float, float]], List[int], List[int]]:
3535
"""Extract box bounds from a search space in the usual Scipy format.
3636
Identify integer parameters as task features.
3737
"""
3838
bounds: List[Tuple[float, float]] = []
3939
task_features: List[int] = []
40+
fidelity_features: List[int] = []
4041
for i, p_name in enumerate(param_names):
4142
p = search_space.parameters[p_name]
4243
# Validation
@@ -48,7 +49,10 @@ def get_bounds_and_task(
4849
bounds.append((p.lower, p.upper))
4950
if p.parameter_type == ParameterType.INT:
5051
task_features.append(i)
51-
return bounds, task_features
52+
if p.is_fidelity:
53+
fidelity_features.append(i)
54+
55+
return bounds, task_features, fidelity_features
5256

5357

5458
def get_fixed_features(

ax/modelbridge/random.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -65,7 +65,7 @@ def _gen(
6565
) -> Tuple[List[ObservationFeatures], List[float], Optional[ObservationFeatures]]:
6666
"""Generate new candidates according to a search_space."""
6767
# Extract parameter values
68-
bounds, _ = get_bounds_and_task(search_space, self.parameters)
68+
bounds, _, _ = get_bounds_and_task(search_space, self.parameters)
6969
# Get fixed features
7070
fixed_features_dict = get_fixed_features(fixed_features, self.parameters)
7171
# Extract param constraints

ax/modelbridge/tests/test_numpy_modelbridge.py

Lines changed: 18 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@
2222
class NumpyModelBridgeTest(TestCase):
2323
def setUp(self):
2424
x = RangeParameter("x", ParameterType.FLOAT, lower=0, upper=1)
25-
y = RangeParameter("y", ParameterType.FLOAT, lower=1, upper=2)
25+
y = RangeParameter("y", ParameterType.FLOAT, lower=1, upper=2, is_fidelity=True)
2626
z = RangeParameter("z", ParameterType.FLOAT, lower=0, upper=5)
2727
self.parameters = [x, y, z]
2828
parameter_constraints = [
@@ -83,16 +83,17 @@ def testFitAndUpdate(self, mock_init):
8383
self.observation_features + [sq_feat],
8484
self.observation_data + [sq_data],
8585
)
86-
self.assertEqual(ma.parameters, ["x", "y", "z"])
86+
self.assertEqual(ma.parameters, ["x", "z", "y"])
8787
self.assertEqual(sorted(ma.outcomes), ["a", "b"])
8888
self.assertEqual(ma.training_in_design, [True, True, True, False])
8989
Xs = {
90-
"a": np.array([[0.2, 1.2, 3.0], [0.4, 1.4, 3.0], [0.6, 1.6, 3]]),
91-
"b": np.array([[0.2, 1.2, 3.0], [0.4, 1.4, 3.0]]),
90+
"a": np.array([[0.2, 3.0, 1.2], [0.4, 3.0, 1.4], [0.6, 3.0, 1.6]]),
91+
"b": np.array([[0.2, 3.0, 1.2], [0.4, 3.0, 1.4]]),
9292
}
9393
Ys = {"a": np.array([[1.0], [2.0], [3.0]]), "b": np.array([[-1.0], [-2.0]])}
9494
Yvars = {"a": np.array([[1.0], [2.0], [3.0]]), "b": np.array([[6.0], [7.0]])}
95-
bounds = [(0.0, 1.0), (1.0, 2.0), (0.0, 5.0)]
95+
# put fidelity parameter to the last column
96+
bounds = [(0.0, 1.0), (0.0, 5.0), (1.0, 2.0)]
9697
model_fit_args = model.fit.mock_calls[0][2]
9798
for i, x in enumerate(model_fit_args["Xs"]):
9899
self.assertTrue(np.array_equal(x, Xs[ma.outcomes[i]]))
@@ -101,7 +102,7 @@ def testFitAndUpdate(self, mock_init):
101102
for i, v in enumerate(model_fit_args["Yvars"]):
102103
self.assertTrue(np.array_equal(v, Yvars[ma.outcomes[i]]))
103104
self.assertEqual(model_fit_args["bounds"], bounds)
104-
self.assertEqual(model_fit_args["feature_names"], ["x", "y", "z"])
105+
self.assertEqual(model_fit_args["feature_names"], ["x", "z", "y"])
105106

106107
# And update
107108
ma.training_in_design.extend([True, True, True, True])
@@ -312,15 +313,24 @@ def testCrossValidate(self, mock_init, mock_cv):
312313
self.assertEqual(od, self.observation_data[i])
313314

314315
def testGetBoundsAndTask(self):
315-
bounds, task_features = get_bounds_and_task(self.search_space, ["x", "y", "z"])
316+
bounds, task_features, fidelity_features = get_bounds_and_task(
317+
self.search_space, ["x", "y", "z"]
318+
)
316319
self.assertEqual(bounds, [(0.0, 1.0), (1.0, 2.0), (0.0, 5.0)])
317320
self.assertEqual(task_features, [])
321+
self.assertEqual(fidelity_features, [1])
322+
bounds, task_features, fidelity_features = get_bounds_and_task(
323+
self.search_space, ["x", "z"]
324+
)
325+
self.assertEqual(fidelity_features, [])
318326
# Test that Int param is treated as task feature
319327
search_space = SearchSpace(self.parameters)
320328
search_space._parameters["x"] = RangeParameter(
321329
"x", ParameterType.INT, lower=1, upper=4
322330
)
323-
bounds, task_features = get_bounds_and_task(search_space, ["x", "y", "z"])
331+
bounds, task_features, fidelity_features = get_bounds_and_task(
332+
search_space, ["x", "y", "z"]
333+
)
324334
self.assertEqual(task_features, [0])
325335
# Test validation
326336
search_space._parameters["x"] = ChoiceParameter(

ax/modelbridge/tests/test_torch_modelbridge.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,7 @@ def testTorchModelBridge(self, mock_init):
4343
bounds=None,
4444
feature_names=[],
4545
task_features=[],
46+
fidelity_features=[],
4647
)
4748
model_fit_args = model.fit.mock_calls[0][2]
4849
self.assertTrue(
@@ -111,7 +112,6 @@ def testTorchModelBridge(self, mock_init):
111112
gen_args = model.gen.mock_calls[0][2]
112113
self.assertEqual(gen_args["n"], 3)
113114
self.assertEqual(gen_args["bounds"], [(0, 1)])
114-
print(gen_args["objective_weights"])
115115
self.assertTrue(
116116
torch.equal(
117117
gen_args["objective_weights"],

ax/modelbridge/torch.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -91,6 +91,7 @@ def _model_fit(
9191
bounds: List[Tuple[float, float]],
9292
task_features: List[int],
9393
feature_names: List[str],
94+
fidelity_features: List[int],
9495
) -> None:
9596
self.model = model
9697
# Convert numpy arrays to torch tensors
@@ -104,6 +105,7 @@ def _model_fit(
104105
bounds=bounds,
105106
task_features=task_features,
106107
feature_names=feature_names,
108+
fidelity_features=fidelity_features,
107109
)
108110

109111
def _model_update(

ax/models/numpy/randomforest.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,7 @@ def fit(
4141
bounds: List[Tuple[float, float]],
4242
task_features: List[int],
4343
feature_names: List[str],
44+
fidelity_features: List[int],
4445
) -> None:
4546
for i, X in enumerate(Xs):
4647
self.models.append(

ax/models/numpy_base.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ def fit(
2222
bounds: List[Tuple[float, float]],
2323
task_features: List[int],
2424
feature_names: List[str],
25+
fidelity_features: List[int],
2526
) -> None:
2627
"""Fit model to m outcomes.
2728
@@ -35,6 +36,8 @@ def fit(
3536
task_features: Columns of X that take integer values and should be
3637
treated as task parameters.
3738
feature_names: Names of each column of X.
39+
fidelity_features: Columns of X that should be treated as fidelity
40+
parameters.
3841
"""
3942
pass
4043

ax/models/tests/test_botorch_defaults.py

Lines changed: 14 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -26,11 +26,23 @@ def test_task_feature(self, gp_mock, get_model_mock):
2626
x = [torch.zeros(2, 2)]
2727
y = [torch.zeros(2, 1)]
2828
yvars = [torch.ones(2, 1)]
29-
get_and_fit_model(Xs=x, Ys=y, Yvars=yvars, task_features=[1], state_dict=[])
29+
get_and_fit_model(
30+
Xs=x,
31+
Ys=y,
32+
Yvars=yvars,
33+
task_features=[1],
34+
fidelity_features=[],
35+
state_dict=[],
36+
)
3037
# Check that task feature was correctly passed to _get_model
3138
self.assertEqual(get_model_mock.mock_calls[0][2]["task_feature"], 1)
3239

3340
with self.assertRaises(ValueError):
3441
get_and_fit_model(
35-
Xs=x, Ys=y, Yvars=yvars, task_features=[0, 1], state_dict=[]
42+
Xs=x,
43+
Ys=y,
44+
Yvars=yvars,
45+
task_features=[0, 1],
46+
fidelity_features=[],
47+
state_dict=[],
3648
)

ax/models/tests/test_botorch_model.py

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,7 @@ def test_BotorchModel(self, dtype=torch.float, cuda=False):
5454
bounds=bounds,
5555
task_features=task_features,
5656
feature_names=feature_names,
57+
fidelity_features=[],
5758
)
5859
_mock_fit_model.assert_called_once()
5960
# Check attributes
@@ -81,6 +82,7 @@ def test_BotorchModel(self, dtype=torch.float, cuda=False):
8182
bounds=bounds,
8283
task_features=task_features,
8384
feature_names=feature_names,
85+
fidelity_features=[],
8486
)
8587
_mock_fit_model.assert_called_once()
8688

@@ -263,7 +265,12 @@ def test_BotorchModel(self, dtype=torch.float, cuda=False):
263265
key: torch.tensor(val, **tkwargs) for key, val in true_state_dict.items()
264266
}
265267
model = get_and_fit_model(
266-
Xs=Xs1, Ys=Ys1, Yvars=Yvars1, task_features=[], state_dict=true_state_dict
268+
Xs=Xs1,
269+
Ys=Ys1,
270+
Yvars=Yvars1,
271+
task_features=[],
272+
fidelity_features=[],
273+
state_dict=true_state_dict,
267274
)
268275
for k, v in chain(model.named_parameters(), model.named_buffers()):
269276
self.assertTrue(torch.equal(true_state_dict[k], v))
@@ -292,6 +299,7 @@ def test_BotorchModelOneOutcome(self):
292299
bounds=bounds,
293300
task_features=task_features,
294301
feature_names=feature_names,
302+
fidelity_features=[],
295303
)
296304
_mock_fit_model.assert_called_once()
297305
X = torch.rand(2, 3, dtype=torch.float)
@@ -321,6 +329,7 @@ def test_BotorchModelConstraints(self):
321329
bounds=bounds,
322330
task_features=task_features,
323331
feature_names=feature_names,
332+
fidelity_features=[],
324333
)
325334
_mock_fit_model.assert_called_once()
326335

0 commit comments

Comments
 (0)