Skip to content

Commit 86b90f0

Browse files
Balandatfacebook-github-bot
authored andcommitted
Revamp qKG constructor to avoid issue with missing objective (#351)
Summary: Pull Request resolved: #351 D18739840 added a check to `MCAcquisitionFunction` that would raise if no objective was specified for multi-output models. This doesn't play well with the current qKG constructor, something that was overlooked in testing. This modifies the qKG constructor to avoid this explicit check. Reviewed By: 2timesjay Differential Revision: D19352464 fbshipit-source-id: eb99329d393be68f5a9c652e1244c63efc2b7d87
1 parent 1242234 commit 86b90f0

File tree

2 files changed

+49
-3
lines changed

2 files changed

+49
-3
lines changed

botorch/acquisition/knowledge_gradient.py

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -103,14 +103,20 @@ def __init__(
103103
)
104104
else:
105105
num_fantasies = sampler.sample_shape[0]
106-
super().__init__(model=model, sampler=sampler, X_pending=X_pending)
106+
super(MCAcquisitionFunction, self).__init__(model=model)
107107
# if not explicitly specified, we use the posterior mean for linear objs
108108
if isinstance(objective, MCAcquisitionObjective) and inner_sampler is None:
109109
inner_sampler = SobolQMCNormalSampler(
110110
num_samples=128, resample=False, collapse_batch_dims=True
111111
)
112-
self.inner_sampler = inner_sampler
112+
if objective is None and model.num_outputs != 1:
113+
raise UnsupportedError(
114+
"Must specify an objective when using a multi-output model."
115+
)
116+
self.sampler = sampler
113117
self.objective = objective
118+
self.set_X_pending(X_pending)
119+
self.inner_sampler = inner_sampler
114120
self.num_fantasies = num_fantasies
115121
self.current_value = current_value
116122

test/acquisition/test_knowledge_gradient.py

Lines changed: 41 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,10 +16,12 @@
1616
qMultiFidelityKnowledgeGradient,
1717
)
1818
from botorch.acquisition.monte_carlo import qSimpleRegret
19-
from botorch.acquisition.objective import GenericMCObjective
19+
from botorch.acquisition.objective import GenericMCObjective, ScalarizedObjective
2020
from botorch.exceptions.errors import UnsupportedError
21+
from botorch.posteriors.gpytorch import GPyTorchPosterior
2122
from botorch.sampling.samplers import IIDNormalSampler, SobolQMCNormalSampler
2223
from botorch.utils.testing import BotorchTestCase, MockModel, MockPosterior
24+
from gpytorch.distributions import MultitaskMultivariateNormal
2325

2426

2527
NO = "botorch.utils.testing.MockModel.num_outputs"
@@ -92,6 +94,20 @@ def test_initialize_q_knowledge_gradient(self):
9294
self.assertIsNone(qKG.X_pending)
9395
self.assertTrue(torch.equal(qKG.current_value, current_value))
9496
self.assertEqual(qKG.get_augmented_q_batch_size(q=3), 8 + 3)
97+
# test construction with non-MC objective (ScalarizedObjective)
98+
qKG_s = qKnowledgeGradient(
99+
model=mm,
100+
num_fantasies=16,
101+
sampler=sampler,
102+
objective=ScalarizedObjective(weights=torch.rand(2)),
103+
)
104+
self.assertIsNone(qKG_s.inner_sampler)
105+
self.assertIsInstance(qKG_s.objective, ScalarizedObjective)
106+
# test error if no objective and multi-output model
107+
mean2 = torch.zeros(1, 2, device=self.device, dtype=dtype)
108+
mm2 = MockModel(MockPosterior(mean=mean2))
109+
with self.assertRaises(UnsupportedError):
110+
qKnowledgeGradient(model=mm2)
95111

96112
def test_evaluate_q_knowledge_gradient(self):
97113
for dtype in (torch.float, torch.double):
@@ -172,6 +188,30 @@ def test_evaluate_q_knowledge_gradient(self):
172188
self.assertEqual(ckwargs["X"].shape, torch.Size([1, 1, 1]))
173189
self.assertTrue(torch.allclose(val, objective(samples).mean(), atol=1e-4))
174190
self.assertTrue(torch.equal(qKG.extract_candidates(X), X[..., :-n_f, :]))
191+
# test non-MC objective (ScalarizedObjective)
192+
weights = torch.rand(2, device=self.device, dtype=dtype)
193+
objective = ScalarizedObjective(weights=weights)
194+
mean = torch.tensor([1.0, 0.5], device=self.device, dtype=dtype).expand(
195+
n_f, 1, 2
196+
)
197+
cov = torch.tensor(
198+
[[1.0, 0.1], [0.1, 0.5]], device=self.device, dtype=dtype
199+
).expand(n_f, 2, 2)
200+
posterior = GPyTorchPosterior(MultitaskMultivariateNormal(mean, cov))
201+
mfm = MockModel(posterior)
202+
with mock.patch.object(MockModel, "fantasize", return_value=mfm) as patch_f:
203+
with mock.patch(NO, new_callable=mock.PropertyMock) as mock_num_outputs:
204+
mock_num_outputs.return_value = 2
205+
mm = MockModel(None)
206+
qKG = qKnowledgeGradient(
207+
model=mm, num_fantasies=n_f, objective=objective
208+
)
209+
val = qKG(X)
210+
patch_f.assert_called_once()
211+
cargs, ckwargs = patch_f.call_args
212+
self.assertEqual(ckwargs["X"].shape, torch.Size([1, 1, 1]))
213+
val_expected = (mean * weights).sum(-1).mean(0)
214+
self.assertTrue(torch.allclose(val, val_expected))
175215

176216

177217
class TestQMultiFidelityKnowledgeGradient(BotorchTestCase):

0 commit comments

Comments
 (0)