4
4
# This source code is licensed under the MIT license found in the
5
5
# LICENSE file in the root directory of this source tree.
6
6
7
+ import math
7
8
from unittest .mock import Mock , patch
8
9
9
10
import numpy as np
10
-
11
11
import torch
12
12
from ax .core .search_space import SearchSpaceDigest
13
13
from ax .exceptions .core import UserInputError
37
37
GaussianLikelihood ,
38
38
Likelihood , # noqa: F401
39
39
)
40
- from gpytorch .mlls import ExactMarginalLogLikelihood , LeaveOneOutPseudoLikelihood
40
+ from gpytorch .mlls import ExactMarginalLogLikelihood
41
+
41
42
42
43
SURROGATE_PATH = f"{ Surrogate .__module__ } "
43
44
UTILS_PATH = f"{ choose_model_class .__module__ } "
@@ -58,6 +59,9 @@ def setUp(self) -> None:
58
59
Xs1 , Ys1 , Yvars1 , bounds , _ , _ , _ = get_torch_test_data (
59
60
dtype = self .dtype , task_features = self .search_space_digest .task_features
60
61
)
62
+ # Change the inputs/outputs a bit so the data isn't identical
63
+ Xs1 [0 ] *= 2
64
+ Ys1 [0 ] += 1
61
65
Xs2 , Ys2 , Yvars2 , _ , _ , _ , _ = get_torch_test_data (
62
66
dtype = self .dtype , task_features = self .search_space_digest .task_features
63
67
)
@@ -352,19 +356,12 @@ def test_fit(
352
356
)
353
357
354
358
def test_with_botorch_transforms (self ) -> None :
355
- input_transforms = {"outcome_1" : Normalize (d = 3 ), "outcome_2" : Normalize (d = 3 )}
356
- outcome_transforms = {
357
- "outcome_1" : Standardize (m = 1 ),
358
- "outcome_2" : Standardize (m = 1 ),
359
- }
359
+ input_transforms = Normalize (d = 3 )
360
+ outcome_transforms = Standardize (m = 1 )
360
361
surrogate = ListSurrogate (
361
362
botorch_submodel_class = SingleTaskGPWithDifferentConstructor ,
362
363
mll_class = ExactMarginalLogLikelihood ,
363
- # pyre-fixme[6]: For 3rd param expected `Optional[Dict[str,
364
- # OutcomeTransform]]` but got `Dict[str, Standardize]`.
365
364
submodel_outcome_transforms = outcome_transforms ,
366
- # pyre-fixme[6]: For 4th param expected `Optional[Dict[str,
367
- # InputTransform]]` but got `Dict[str, Normalize]`.
368
365
submodel_input_transforms = input_transforms ,
369
366
)
370
367
with self .assertRaisesRegex (UserInputError , "The BoTorch model class" ):
@@ -375,23 +372,34 @@ def test_with_botorch_transforms(self) -> None:
375
372
surrogate = ListSurrogate (
376
373
botorch_submodel_class = SingleTaskGP ,
377
374
mll_class = ExactMarginalLogLikelihood ,
378
- # pyre-fixme[6]: For 3rd param expected `Optional[Dict[str,
379
- # OutcomeTransform]]` but got `Dict[str, Standardize]`.
380
375
submodel_outcome_transforms = outcome_transforms ,
381
- # pyre-fixme[6]: For 4th param expected `Optional[Dict[str,
382
- # InputTransform]]` but got `Dict[str, Normalize]`.
383
376
submodel_input_transforms = input_transforms ,
384
377
)
385
378
surrogate .construct (
386
379
datasets = self .supervised_training_data ,
387
380
metric_names = self .outcomes ,
388
381
)
389
- models = surrogate .model .models
390
- for i , outcome in enumerate (("outcome_1" , "outcome_2" )):
391
- # pyre-fixme[29]: `Union[BoundMethod[typing.Callable(torch._C._TensorBase...
392
- self .assertIs (models [i ].outcome_transform , outcome_transforms [outcome ])
393
- # pyre-fixme[29]: `Union[BoundMethod[typing.Callable(torch._C._TensorBase...
394
- self .assertIs (models [i ].input_transform , input_transforms [outcome ])
382
+ # pyre-ignore [9]
383
+ models : torch .nn .modules .container .ModuleList = surrogate .model .models
384
+ for i in range (2 ):
385
+ self .assertIsInstance (models [i ].outcome_transform , Standardize )
386
+ self .assertIsInstance (models [i ].input_transform , Normalize )
387
+ self .assertEqual (models [0 ].outcome_transform .means .item (), 4.5 )
388
+ self .assertEqual (models [1 ].outcome_transform .means .item (), 3.5 )
389
+ self .assertAlmostEqual (
390
+ models [0 ].outcome_transform .stdvs .item (), 1 / math .sqrt (2 )
391
+ )
392
+ self .assertAlmostEqual (
393
+ models [1 ].outcome_transform .stdvs .item (), 1 / math .sqrt (2 )
394
+ )
395
+ self .assertTrue (
396
+ torch .all (
397
+ torch .isclose (
398
+ models [0 ].input_transform .bounds ,
399
+ 2 * models [1 ].input_transform .bounds , # pyre-ignore
400
+ )
401
+ )
402
+ )
395
403
396
404
def test_serialize_attributes_as_kwargs (self ) -> None :
397
405
expected = self .surrogate .__dict__
@@ -411,48 +419,64 @@ def test_serialize_attributes_as_kwargs(self) -> None:
411
419
self .assertEqual (self .surrogate ._serialize_attributes_as_kwargs (), expected )
412
420
413
421
def test_construct_custom_model (self ) -> None :
414
- noise_con1 , noise_con2 = Interval (1e-6 , 1e-1 ), GreaterThan (1e-4 )
415
- surrogate = ListSurrogate (
416
- botorch_submodel_class = SingleTaskGP ,
417
- mll_class = LeaveOneOutPseudoLikelihood ,
418
- submodel_covar_module_class = {
419
- "outcome_1" : RBFKernel ,
420
- "outcome_2" : MaternKernel ,
421
- },
422
- submodel_covar_module_options = {
423
- "outcome_1" : {"ard_num_dims" : 1 },
424
- "outcome_2" : {"ard_num_dims" : 3 },
425
- },
426
- submodel_likelihood_class = {
427
- "outcome_1" : GaussianLikelihood ,
428
- "outcome_2" : GaussianLikelihood ,
429
- },
430
- submodel_likelihood_options = {
431
- "outcome_1" : {"noise_constraint" : noise_con1 },
432
- "outcome_2" : {"noise_constraint" : noise_con2 },
433
- },
434
- )
435
- surrogate .construct (
436
- datasets = self .supervised_training_data ,
437
- metric_names = self .outcomes ,
438
- )
439
- # pyre-fixme[16]: Optional type has no attribute `models`.
440
- self .assertEqual (len (surrogate ._model .models ), 2 )
441
- self .assertEqual (surrogate .mll_class , LeaveOneOutPseudoLikelihood )
442
- for i , m in enumerate (surrogate ._model .models ):
443
- self .assertEqual (type (m .likelihood ), GaussianLikelihood )
444
- if i == 0 :
445
- self .assertEqual (type (m .covar_module ), RBFKernel )
446
- self .assertEqual (m .covar_module .ard_num_dims , 1 )
447
- self .assertEqual (
448
- m .likelihood .noise_covar .raw_noise_constraint , noise_con1
449
- )
450
- else :
422
+ noise_constraint = Interval (1e-4 , 10.0 )
423
+ for submodel_covar_module_options , submodel_likelihood_options in [
424
+ [{"ard_num_dims" : 3 }, {"noise_constraint" : noise_constraint }],
425
+ [{}, {}],
426
+ ]:
427
+ surrogate = ListSurrogate (
428
+ botorch_submodel_class = SingleTaskGP ,
429
+ mll_class = ExactMarginalLogLikelihood ,
430
+ submodel_covar_module_class = MaternKernel ,
431
+ submodel_covar_module_options = submodel_covar_module_options ,
432
+ submodel_likelihood_class = GaussianLikelihood ,
433
+ submodel_likelihood_options = submodel_likelihood_options ,
434
+ submodel_input_transforms = Normalize (d = 3 ),
435
+ submodel_outcome_transforms = Standardize (m = 1 ),
436
+ )
437
+ surrogate .construct (
438
+ datasets = self .supervised_training_data ,
439
+ metric_names = self .outcomes ,
440
+ )
441
+ # pyre-fixme[16]: Optional type has no attribute `models`.
442
+ self .assertEqual (len (surrogate ._model .models ), 2 )
443
+ self .assertEqual (surrogate .mll_class , ExactMarginalLogLikelihood )
444
+ # Make sure we properly copied the transforms
445
+ self .assertNotEqual (
446
+ id (surrogate ._model .models [0 ].input_transform ),
447
+ id (surrogate ._model .models [1 ].input_transform ),
448
+ )
449
+ self .assertNotEqual (
450
+ id (surrogate ._model .models [0 ].outcome_transform ),
451
+ id (surrogate ._model .models [1 ].outcome_transform ),
452
+ )
453
+
454
+ for m in surrogate ._model .models :
455
+ self .assertEqual (type (m .likelihood ), GaussianLikelihood )
451
456
self .assertEqual (type (m .covar_module ), MaternKernel )
452
- self .assertEqual (m .covar_module .ard_num_dims , 3 )
453
- self .assertEqual (
454
- m .likelihood .noise_covar .raw_noise_constraint , noise_con2
455
- )
457
+ if submodel_covar_module_options :
458
+ self .assertEqual (m .covar_module .ard_num_dims , 3 )
459
+ else :
460
+ self .assertEqual (m .covar_module .ard_num_dims , None )
461
+ if submodel_likelihood_options :
462
+ self .assertEqual (
463
+ type (m .likelihood .noise_covar .raw_noise_constraint ), Interval
464
+ )
465
+ self .assertEqual (
466
+ m .likelihood .noise_covar .raw_noise_constraint .lower_bound ,
467
+ noise_constraint .lower_bound ,
468
+ )
469
+ self .assertEqual (
470
+ m .likelihood .noise_covar .raw_noise_constraint .upper_bound ,
471
+ noise_constraint .upper_bound ,
472
+ )
473
+ else :
474
+ self .assertEqual (
475
+ type (m .likelihood .noise_covar .raw_noise_constraint ), GreaterThan
476
+ )
477
+ self .assertEqual (
478
+ m .likelihood .noise_covar .raw_noise_constraint .lower_bound , 1e-4
479
+ )
456
480
457
481
def test_w_robust_digest (self ) -> None :
458
482
surrogate = ListSurrogate (
@@ -470,7 +494,7 @@ def test_w_robust_digest(self) -> None:
470
494
"environmental_variables" : [],
471
495
"multiplicative" : False ,
472
496
}
473
- surrogate .submodel_input_transforms = { self . outcomes [ 0 ]: Normalize (d = 1 )}
497
+ surrogate .submodel_input_transforms = Normalize (d = 1 )
474
498
with self .assertRaisesRegex (NotImplementedError , "input transforms" ):
475
499
surrogate .construct (
476
500
datasets = self .supervised_training_data ,
0 commit comments