Repository: cornellius-gp/gpytorch
Pull request #713 (2019-05-30 20:44:52)

Add support for get_fantasy_model to IndependentModelList
Addresses #712


Patch:
diff --git a/gpytorch/models/model_list.py b/gpytorch/models/model_list.py
index af97d2a2..802e842d 100644
--- a/gpytorch/models/model_list.py
+++ b/gpytorch/models/model_list.py
@@ -47,6 +47,35 @@ class IndependentModelList(AbstractModelList):
     def forward(self, *args, **kwargs):
         [model.forward(*args_, **kwargs) for model, args_ in zip(self.models, _get_tensor_args(*args))]
 
+    def get_fantasy_model(self, inputs, targets, **kwargs):
+        """
+        Returns a new GP model that incorporates the specified inputs and targets as new training data.
+
+        This is a simple wrapper that creates fantasy models for each of the models in the model list,
+        and returns the same class of fantasy models.
+
+        Args:
+            - :attr:`inputs`: List of locations of fantasy observations, one for each model.
+            - :attr:`targets` List of labels of fantasy observations, one for each model.
+
+        Returns:
+            - :class:`IndependentModelList`
+                An `IndependentModelList` model, where each sub-model is the fantasy model of the respective
+                sub-model in the original model at the corresponding input locations / labels.
+        """
+
+        if "noise" in kwargs:
+            noise = kwargs.pop("noise")
+            kwargs = [{**kwargs, "noise": noise_} if noise_ is not None else kwargs for noise_ in noise]
+        else:
+            kwargs = [kwargs] * len(inputs)
+
+        fantasy_models = [
+            model.get_fantasy_model(*inputs_, *targets_, **kwargs_) for model, inputs_, targets_, kwargs_
+            in zip(self.models, _get_tensor_args(*inputs), _get_tensor_args(*targets), kwargs)
+        ]
+        return self.__class__(*fantasy_models)
+
     def __call__(self, *args, **kwargs):
         return [model.__call__(*args_, **kwargs) for model, args_ in zip(self.models, _get_tensor_args(*args))]
 


Test patch:
diff --git a/test/models/test_model_list.py b/test/models/test_model_list.py
new file mode 100644
index 00000000..cd87b12f
--- /dev/null
+++ b/test/models/test_model_list.py
@@ -0,0 +1,56 @@
+#!/usr/bin/env python3
+
+import torch
+import unittest
+
+from gpytorch.likelihoods import FixedNoiseGaussianLikelihood
+from gpytorch.models import IndependentModelList
+from test.models.test_exact_gp import TestExactGP
+
+
+class TestModelListGP(unittest.TestCase):
+
+    def create_model(self, fixed_noise=False):
+        data = TestExactGP.create_test_data(self)
+        likelihood, labels = TestExactGP.create_likelihood_and_labels(self)
+        if fixed_noise:
+            noise = 0.1 + 0.2 * torch.rand_like(labels)
+            likelihood = FixedNoiseGaussianLikelihood(noise)
+        return TestExactGP.create_model(self, data, labels, likelihood)
+
+    def test_forward_eval(self):
+        models = [self.create_model() for _ in range(2)]
+        model = IndependentModelList(*models)
+        model.eval()
+        model(torch.rand(3))
+
+    def test_forward_eval_fixed_noise(self):
+        models = [self.create_model(fixed_noise=True) for _ in range(2)]
+        model = IndependentModelList(*models)
+        model.eval()
+        model(torch.rand(3))
+
+    def test_get_fantasy_model(self):
+        models = [self.create_model() for _ in range(2)]
+        model = IndependentModelList(*models)
+        model.eval()
+        model(torch.rand(3), torch.rand(3))
+        fant_x = [torch.randn(2), torch.randn(3)]
+        fant_y = [torch.randn(2), torch.randn(3)]
+        fmodel = model.get_fantasy_model(fant_x, fant_y)
+        fmodel(torch.randn(4))
+
+    def test_get_fantasy_model_fixed_noise(self):
+        models = [self.create_model(fixed_noise=True) for _ in range(2)]
+        model = IndependentModelList(*models)
+        model.eval()
+        model(torch.rand(3), torch.rand(3))
+        fant_x = [torch.randn(2), torch.randn(3)]
+        fant_y = [torch.randn(2), torch.randn(3)]
+        fant_noise = [0.1 * torch.ones(2), 0.1 * torch.ones(3)]
+        fmodel = model.get_fantasy_model(fant_x, fant_y, noise=fant_noise)
+        fmodel(torch.randn(4))
+
+
+if __name__ == "__main__":
+    unittest.main()