Repository: cornellius-gp/gpytorch

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.


Discussion:
should be relatively straightforward to do if you pass in the full name (`module.submodule.submodule.parameter_name`)
I think the module.submodule.parameter_name pattern seems good

Resolving patch:
diff --git a/gpytorch/module.py b/gpytorch/module.py
--- a/gpytorch/module.py
+++ b/gpytorch/module.py
@@ -47,14 +47,26 @@ def initialize(self, **kwargs):
         """
         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
--- a/test/examples/test_simple_gp_regression.py
+++ b/test/examples/test_simple_gp_regression.py
@@ -82,6 +82,24 @@ def test_prior_cuda(self):
         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