Repository: GPflow/GPflow

Issue:
Anisotropic lengthscales
Currently, by default, all stationary kernels impose a positive transform on the lengthscales Parameter. For the case of AnisotropicStationary kernels, this reduces the viable parameter space by a factor of 2^(D-1), where D is the number of input dimensions. So it is acceptable in the case of a 1D problem, but in higher dimensions we would miss out. For example, for the Cosine kernel there is a significant difference between lengthscales of [+1, +1], and [+1, -1], they correspond to waves propagating in perpendicular directions. 



Discussion:

Resolving patch:
diff --git a/gpflow/kernels/stationaries.py b/gpflow/kernels/stationaries.py
--- a/gpflow/kernels/stationaries.py
+++ b/gpflow/kernels/stationaries.py
@@ -41,7 +41,7 @@ def __init__(
         :param variance: the (initial) value for the variance parameter.
         :param lengthscales: the (initial) value for the lengthscale
             parameter(s), to induce ARD behaviour this must be initialised as
-            an array the same length as the the number of active dimensions
+            an array the same length as the number of active dimensions
             e.g. [1., 1., 1.]. If only a single value is passed, this value
             is used as the lengthscale of each dimension.
         :param kwargs: accepts `name` and `active_dims`, which is a list or
@@ -122,6 +122,26 @@ class AnisotropicStationary(Stationary):
     input dimension.
     """
 
+    def __init__(
+        self, variance: TensorType = 1.0, lengthscales: TensorType = 1.0, **kwargs: Any
+    ) -> None:
+        """
+        :param variance: the (initial) value for the variance parameter.
+        :param lengthscales: the (initial) value for the lengthscale
+            parameter(s), to induce ARD behaviour this must be initialised as
+            an array the same length as the number of active dimensions
+            e.g. [1., 1., 1.]. Note that anisotropic kernels can possess
+            negative lengthscales. If only a single value is passed, this
+            value is used as the lengthscale of each dimension.
+        :param kwargs: accepts `name` and `active_dims`, which is a list or
+            slice of indices which controls which columns of X are used (by
+            default, all columns are used).
+        """
+        super().__init__(variance, lengthscales, **kwargs)
+
+        if self.ard:
+            self.lengthscales = Parameter(self.lengthscales.numpy())
+
     def K(self, X: TensorType, X2: Optional[TensorType] = None) -> tf.Tensor:
         return self.K_d(self.scaled_difference_matrix(X, X2))
 


Test patch:
diff --git a/tests/gpflow/kernels/test_kernels.py b/tests/gpflow/kernels/test_kernels.py
--- a/tests/gpflow/kernels/test_kernels.py
+++ b/tests/gpflow/kernels/test_kernels.py
@@ -19,7 +19,6 @@
 import tensorflow as tf
 from numpy.testing import assert_allclose
 
-import gpflow
 import gpflow.ci_utils
 from gpflow.base import AnyNDArray, TensorType
 from gpflow.config import default_float
@@ -31,6 +30,7 @@
     Constant,
     Convolutional,
     Coregion,
+    Cosine,
     IsotropicStationary,
     Kernel,
     Linear,
@@ -467,6 +467,7 @@ def test_ard_invalid_active_dims() -> None:
         [SquaredExponential, "lengthscales"],
         [Linear, "variance"],
         [ArcCosine, "weight_variances"],
+        [Cosine, "lengthscales"],
     ],
 )
 @pytest.mark.parametrize(