Repository: cornellius-gp/gpytorch
Pull request #643 (2019-04-12 22:00:40)

[RFC] Add initial_value property to constraints.
This simplifies intializing parameters from a constraint object. Addresses #629


Patch:
diff --git a/gpytorch/constraints/constraints.py b/gpytorch/constraints/constraints.py
index 2aa8b3a4..30b5b4af 100644
--- a/gpytorch/constraints/constraints.py
+++ b/gpytorch/constraints/constraints.py
@@ -9,13 +9,14 @@ from .. import settings
 
 
 class Interval(Module):
-    def __init__(self, lower_bound, upper_bound, transform=sigmoid, inv_transform=inv_sigmoid):
+    def __init__(self, lower_bound, upper_bound, transform=sigmoid, inv_transform=inv_sigmoid, initial_value=None):
         """
         Defines an interval constraint for GP model parameters, specified by a lower bound and upper bound. For usage
         details, see the documentation for :meth:`~gpytorch.module.Module.register_constraint`.
 
         Args:
-            lower_bound (float or torch.Tensor):
+            lower_bound (float or torch.Tensor): The lower bound on the parameter.
+            upper_bound (float or torch.Tensor): The upper bound on the parameter.
         """
         lower_bound = torch.as_tensor(lower_bound)
         upper_bound = torch.as_tensor(upper_bound)
@@ -30,6 +31,7 @@ class Interval(Module):
 
         self._transform = transform
         self._inv_transform = inv_transform
+        self._initial_value = initial_value
 
         if transform is not None and inv_transform is None:
             self._inv_transform = _get_inv_param_transform(transform)
@@ -117,6 +119,13 @@ class Interval(Module):
 
         return tensor
 
+    @property
+    def initial_value(self):
+        """
+        The initial parameter value (if specified, None otherwise)
+        """
+        return self._initial_value
+
     def __repr__(self):
         if self.lower_bound.numel() == 1 and self.upper_bound.numel() == 1:
             return self._get_name() + f"({self.lower_bound:.3E}, {self.upper_bound:.3E})"
@@ -129,9 +138,13 @@ class Interval(Module):
 
 
 class GreaterThan(Interval):
-    def __init__(self, lower_bound, transform=softplus, inv_transform=inv_softplus):
+    def __init__(self, lower_bound, transform=softplus, inv_transform=inv_softplus, initial_value=None):
         super().__init__(
-            lower_bound=lower_bound, upper_bound=math.inf, transform=transform, inv_transform=inv_transform
+            lower_bound=lower_bound,
+            upper_bound=math.inf,
+            transform=transform,
+            inv_transform=inv_transform,
+            initial_value=initial_value,
         )
 
     def __repr__(self):
@@ -150,8 +163,8 @@ class GreaterThan(Interval):
 
 
 class Positive(GreaterThan):
-    def __init__(self, transform=softplus, inv_transform=inv_softplus):
-        super().__init__(lower_bound=0.0, transform=transform, inv_transform=inv_transform)
+    def __init__(self, transform=softplus, inv_transform=inv_softplus, initial_value=None):
+        super().__init__(lower_bound=0.0, transform=transform, inv_transform=inv_transform, initial_value=initial_value)
 
     def __repr__(self):
         return self._get_name() + "()"
diff --git a/gpytorch/module.py b/gpytorch/module.py
index ef8bde13..1c6fd774 100644
--- a/gpytorch/module.py
+++ b/gpytorch/module.py
@@ -238,6 +238,10 @@ class Module(nn.Module):
         self.add_module(constraint_name, new_constraint)
         self._constraints[constraint_name] = new_constraint
 
+        # re-initialize the parameter if the constraint specifies an initial value
+        if new_constraint.initial_value is not None:
+            self.initialize(**{param_name: new_constraint.initial_value})
+
     def constraint_for_parameter_name(self, param_name):
         base_module = self
         base_name = param_name


Test patch:
diff --git a/test/constraints/test_constraints.py b/test/constraints/test_constraints.py
index a1597c27..186e5fb3 100644
--- a/test/constraints/test_constraints.py
+++ b/test/constraints/test_constraints.py
@@ -62,6 +62,11 @@ class TestInterval(unittest.TestCase, BaseTestCase):
 
         self.assertAllClose(v, value)
 
+    def test_initial_value(self):
+        constraint = gpytorch.constraints.Interval(1., 5., transform=None, initial_value=3.)
+        lkhd = gpytorch.likelihoods.GaussianLikelihood(noise_constraint=constraint)
+        self.assertEqual(lkhd.noise.item(), 3.)
+
 
 class TestGreaterThan(unittest.TestCase, BaseTestCase):
     def test_transform_float_greater_than(self):
@@ -207,7 +212,7 @@ class TestConstraintNaming(unittest.TestCase, BaseTestCase):
         likelihood = gpytorch.likelihoods.GaussianLikelihood()
         model = ExactGPModel(None, None, likelihood)
 
-        for name, param, constraint in model.named_parameters_and_constraints():
+        for name, _param, constraint in model.named_parameters_and_constraints():
             if name == "likelihood.noise_covar.raw_noise":
                 self.assertIsInstance(constraint, gpytorch.constraints.GreaterThan)
             elif name == "mean_module.constant":