Repository: cornellius-gp/gpytorch

Issue:
Module.initialize should work with transformed parameters
`Module.initialize` right now only works for actual parameters. It should also work for transformed parameters.

For example, the following lines work:

```python
likelihood = gpytorch.likelihoods.GaussianLikelihood(param_transform=torch.exp)
likelihood.initialize(raw_noise=math.log(5))
print(likelihood.noise.item())  #5.
```

Similarly:
```python
likelihood = gpytorch.likelihoods.GaussianLikelihood(param_transform=torch.exp)
likelihood.initialize(raw_noise=torch.tensor(math.log(5)))
print(likelihood.noise.item())  #5.
```

The following line silently errors:
```python
likelihood = gpytorch.likelihoods.GaussianLikelihood(param_transform=torch.exp)
likelihood.initialize(noise=5)
print(likelihood.noise.item())  #1.
```

Discussion:

Patch:
diff --git a/gpytorch/kernels/cosine_kernel.py b/gpytorch/kernels/cosine_kernel.py
index ba34165f..90a5034e 100644
--- a/gpytorch/kernels/cosine_kernel.py
+++ b/gpytorch/kernels/cosine_kernel.py
@@ -91,7 +91,9 @@ class CosineKernel(Kernel):
         return self._set_period_length(value)
 
     def _set_period_length(self, value):
-        self.initialize(log_period_length=self._inv_param_transform(value))
+        if not torch.is_tensor(value):
+            value = torch.tensor(value)
+        self.initialize(raw_period_length=self._inv_param_transform(value))
 
     def forward(self, x1, x2, **params):
         x1_ = x1.div(self.period_length)
diff --git a/gpytorch/kernels/kernel.py b/gpytorch/kernels/kernel.py
index 9de5aa0d..b85ed3c3 100644
--- a/gpytorch/kernels/kernel.py
+++ b/gpytorch/kernels/kernel.py
@@ -138,7 +138,10 @@ class Kernel(Module):
     def _set_lengthscale(self, value):
         if not self.has_lengthscale:
             raise RuntimeError("Kernel has no lengthscale.")
-        self.initialize(log_lengthscale=self._inv_param_transform(value))
+
+        if not torch.is_tensor(value):
+            value = torch.tensor(value)
+        self.initialize(raw_lengthscale=self._inv_param_transform(value))
 
     @property
     def has_custom_exact_predictions(self):
diff --git a/gpytorch/kernels/periodic_kernel.py b/gpytorch/kernels/periodic_kernel.py
index b7a1a2cf..db236e39 100644
--- a/gpytorch/kernels/periodic_kernel.py
+++ b/gpytorch/kernels/periodic_kernel.py
@@ -114,6 +114,8 @@ class PeriodicKernel(Kernel):
         self._set_period_length(value)
 
     def _set_period_length(self, value):
+        if not torch.is_tensor(value):
+            value = torch.tensor(value)
         self.initialize(raw_period_length=self._inv_param_transform(value))
 
     def forward(self, x1, x2, **params):
diff --git a/gpytorch/kernels/scale_kernel.py b/gpytorch/kernels/scale_kernel.py
index 69fd4a66..6030997a 100644
--- a/gpytorch/kernels/scale_kernel.py
+++ b/gpytorch/kernels/scale_kernel.py
@@ -83,7 +83,9 @@ class ScaleKernel(Kernel):
         self._set_outputscale(value)
 
     def _set_outputscale(self, value):
-        self.initialize(log_outputscale=self._inv_param_transform(value))
+        if not torch.is_tensor(value):
+            value = torch.tensor(value)
+        self.initialize(raw_outputscale=self._inv_param_transform(value))
 
     def forward(self, x1, x2, batch_dims=None, **params):
         outputscales = self.outputscale
diff --git a/gpytorch/kernels/spectral_mixture_kernel.py b/gpytorch/kernels/spectral_mixture_kernel.py
index 9dcb43cf..6464d8d1 100644
--- a/gpytorch/kernels/spectral_mixture_kernel.py
+++ b/gpytorch/kernels/spectral_mixture_kernel.py
@@ -118,14 +118,41 @@ class SpectralMixtureKernel(Kernel):
     def mixture_scales(self):
         return self._param_transform(self.raw_mixture_scales).clamp(self.eps, 1e5)
 
+    @mixture_scales.setter
+    def mixture_scales(self, value):
+        self._set_mixture_scales(value)
+
+    def _set_mixture_scales(self, value):
+        if not torch.is_tensor(value):
+            value = torch.tensor(value)
+        self.initialize(raw_mixture_scales=self._inv_param_transform(value))
+
     @property
     def mixture_means(self):
         return self._param_transform(self.raw_mixture_means).clamp(self.eps, 1e5)
 
+    @mixture_means.setter
+    def mixture_means(self, value):
+        self._set_mixture_means(value)
+
+    def _set_mixture_means(self, value):
+        if not torch.is_tensor(value):
+            value = torch.tensor(value)
+        self.initialize(raw_mixture_means=self._inv_param_transform(value))
+
     @property
     def mixture_weights(self):
         return self._param_transform(self.raw_mixture_weights).clamp(self.eps, 1e5)
 
+    @mixture_weights.setter
+    def mixture_weights(self, value):
+        self._set_mixture_weights(value)
+
+    def _set_mixture_weights(self, value):
+        if not torch.is_tensor(value):
+            value = torch.tensor(value)
+        self.initialize(raw_mixture_weights=self._inv_param_transform(value))
+
     def initialize_from_data(self, train_x, train_y, **kwargs):
         if not torch.is_tensor(train_x) or not torch.is_tensor(train_y):
             raise RuntimeError("train_x and train_y should be tensors")
diff --git a/gpytorch/likelihoods/gaussian_likelihood.py b/gpytorch/likelihoods/gaussian_likelihood.py
index d5601231..a0fb48a6 100644
--- a/gpytorch/likelihoods/gaussian_likelihood.py
+++ b/gpytorch/likelihoods/gaussian_likelihood.py
@@ -68,7 +68,7 @@ class GaussianLikelihood(_GaussianLikelihoodBase):
 
     @raw_noise.setter
     def raw_noise(self, value):
-        self.noise_covar.raw_noise = value
+        self.noise_covar.initialize(raw_noise=value)
 
     def variational_log_probability(self, input, target):
         mean, variance = input.mean, input.variance
diff --git a/gpytorch/likelihoods/multitask_gaussian_likelihood.py b/gpytorch/likelihoods/multitask_gaussian_likelihood.py
index 1651b48a..7014ef6f 100644
--- a/gpytorch/likelihoods/multitask_gaussian_likelihood.py
+++ b/gpytorch/likelihoods/multitask_gaussian_likelihood.py
@@ -179,6 +179,8 @@ class MultitaskGaussianLikelihood(_MultitaskGaussianLikelihoodBase):
         self._set_noise(value)
 
     def _set_noise(self, value):
+        if not torch.is_tensor(value):
+            value = torch.tensor(value)
         self.initialize(raw_noise=self._inv_param_transform(value))
 
     def forward(self, input, *params):
diff --git a/gpytorch/likelihoods/noise_models.py b/gpytorch/likelihoods/noise_models.py
index 805570bb..a331b2bc 100644
--- a/gpytorch/likelihoods/noise_models.py
+++ b/gpytorch/likelihoods/noise_models.py
@@ -29,6 +29,8 @@ class _HomoskedasticNoiseBase(Module):
         self._set_noise(value)
 
     def _set_noise(self, value):
+        if not torch.is_tensor(value):
+            value = torch.tensor(value)
         self.initialize(raw_noise=self._inv_param_transform(value))
 
     def forward(self, *params, shape=None):
diff --git a/gpytorch/module.py b/gpytorch/module.py
index 3f0461c3..7b580359 100644
--- a/gpytorch/module.py
+++ b/gpytorch/module.py
@@ -85,8 +85,14 @@ class Module(nn.Module):
 
             if not hasattr(self, name):
                 raise AttributeError("Unknown parameter {p} for {c}".format(p=name, c=self.__class__.__name__))
-            if torch.is_tensor(val):
-                self.__getattr__(name).data.copy_(val)
+            elif name not in self._parameters:
+                setattr(self, name, val)
+            elif torch.is_tensor(val):
+                try:
+                    self.__getattr__(name).data.copy_(val.expand_as(self.__getattr__(name)))
+                except RuntimeError:
+                    self.__getattr__(name).data.copy_(val.view_as(self.__getattr__(name)))
+
             elif isinstance(val, float):
                 self.__getattr__(name).data.fill_(val)
             else:


Test patch:
diff --git a/test/kernels/test_rbf_kernel.py b/test/kernels/test_rbf_kernel.py
index 878fd4f2..740fadf5 100644
--- a/test/kernels/test_rbf_kernel.py
+++ b/test/kernels/test_rbf_kernel.py
@@ -174,6 +174,19 @@ class TestRBFKernel(unittest.TestCase):
 
         self.assertLess(torch.norm(res - actual_param_grad), 1e-5)
 
+    def test_initialize_lengthscale(self):
+        kernel = RBFKernel()
+        kernel.initialize(lengthscale=3.14)
+        actual_value = torch.tensor(3.14).view_as(kernel.lengthscale)
+        self.assertLess(torch.norm(kernel.lengthscale - actual_value), 1e-5)
+
+    def test_initialize_lengthscale_batch(self):
+        kernel = RBFKernel(batch_size=2)
+        ls_init = torch.tensor([3.14, 4.13])
+        kernel.initialize(lengthscale=ls_init)
+        actual_value = ls_init.view_as(kernel.lengthscale)
+        self.assertLess(torch.norm(kernel.lengthscale - actual_value), 1e-5)
+
 
 if __name__ == "__main__":
     unittest.main()
diff --git a/test/kernels/test_scale_kernel.py b/test/kernels/test_scale_kernel.py
index 9a0c51f4..bdebd03f 100644
--- a/test/kernels/test_scale_kernel.py
+++ b/test/kernels/test_scale_kernel.py
@@ -76,6 +76,19 @@ class TestScaleKernel(unittest.TestCase):
         actual = torch.cat([actual[i].diag().unsqueeze(0) for i in range(actual.size(0))])
         self.assertLess(torch.norm(res - actual), 1e-5)
 
+    def test_initialize_outputscale(self):
+        kernel = ScaleKernel(RBFKernel())
+        kernel.initialize(outputscale=3.14)
+        actual_value = torch.tensor(3.14).view_as(kernel.outputscale)
+        self.assertLess(torch.norm(kernel.outputscale - actual_value), 1e-5)
+
+    def test_initialize_outputscale_batch(self):
+        kernel = ScaleKernel(RBFKernel(), batch_size=2)
+        ls_init = torch.tensor([3.14, 4.13])
+        kernel.initialize(outputscale=ls_init)
+        actual_value = ls_init.view_as(kernel.outputscale)
+        self.assertLess(torch.norm(kernel.outputscale - actual_value), 1e-5)
+
 
 if __name__ == "__main__":
     unittest.main()