Repository: cornellius-gp/gpytorch (python)

Issue:
Recursively initialize Module parameters
Say I have an `ExactGP` called `gp`. It would be great if I could just do `gp.initialize(kwargs)` and all the parameters will be initialized recursively. This would allow us to put all the initialization values in one place. 

Note that it would have to raise an error if there were a parameter name collision.

Pull request description:
Support recursively initializing parameters
closes #476

Patch:
diff --git a/gpytorch/module.py b/gpytorch/module.py
index 7aa1044f..db418805 100644
--- a/gpytorch/module.py
+++ b/gpytorch/module.py
@@ -47,14 +47,26 @@ class Module(nn.Module):
         """
         Set a value for a parameter
 
-        kwargs: (param_name, value) - parameter to initialize
+        kwargs: (param_name, value) - parameter to initialize.
+        Can also initialize recursively by passing in the full name of a
+        parameter. For example if model has attribute model.likelihood,
+        we can initialize the noise with either
+        `model.initialize(**{'likelihood.noise': 0.1})`
+        or
+        `model.likelihood.initialize(noise=0.1)`.
+        The former method would allow users to more easily store the
+        initialization values as one object.
+
         Value can take the form of a tensor, a float, or an int
         """
 
         for name, val in kwargs.items():
             if isinstance(val, int):
                 val = float(val)
-            if not hasattr(self, name):
+            if '.' in name:
+                module, name = self._get_module_and_name(name)
+                module.initialize(**{name: val})
+            elif not hasattr(self, name):
                 raise AttributeError("Unknown parameter {p} for {c}".format(p=name, c=self.__class__.__name__))
             elif name not in self._parameters:
                 setattr(self, name, val)


Test patch:
diff --git a/test/examples/test_simple_gp_regression.py b/test/examples/test_simple_gp_regression.py
index d39ab944..f4db8e86 100644
--- a/test/examples/test_simple_gp_regression.py
+++ b/test/examples/test_simple_gp_regression.py
@@ -82,6 +82,24 @@ class TestSimpleGPRegression(unittest.TestCase):
         if torch.cuda.is_available():
             self.test_prior(cuda=True)
 
+    def test_recursive_initialize(self, cuda=False):
+        train_x, test_x, train_y, test_y = self._get_data(cuda=cuda)
+
+        likelihood_1 = GaussianLikelihood()
+        gp_model_1 = ExactGPModel(train_x, train_y, likelihood_1)
+
+        likelihood_2 = GaussianLikelihood()
+        gp_model_2 = ExactGPModel(train_x, train_y, likelihood_2)
+
+        gp_model_1.initialize(**{"likelihood.noise": 1e-2,
+                                 "covar_module.base_kernel.lengthscale": 1e-1})
+        gp_model_2.likelihood.initialize(noise=1e-2)
+        gp_model_2.covar_module.base_kernel.initialize(lengthscale=1e-1)
+        self.assertTrue(torch.equal(gp_model_1.likelihood.noise,
+                                    gp_model_2.likelihood.noise))
+        self.assertTrue(torch.equal(gp_model_1.covar_module.base_kernel.lengthscale,
+                                    gp_model_2.covar_module.base_kernel.lengthscale))
+
     def test_posterior_latent_gp_and_likelihood_without_optimization(self, cuda=False):
         train_x, test_x, train_y, test_y = self._get_data(cuda=cuda)
         # We're manually going to set the hyperparameters to be ridiculous