Trouble using Data Containers with GPs

Not sure if this is a bug or just a short between the chair and the keyboard. =)

I’m trying to use a data container with a GP, and the GP doesn’t seem to be able to handle it.

Here’s a model in which I have a GP as part of a hierarchical model. This is simplified, so it doesn’t mean much in itself, but replicates the error.

n_samples = 40
x_sampled = np.random.uniform(-1, 1, size=(n_samples,1))
t_sampled = np.random.uniform(-1, 1, size=(1,n_samples))
z_observations = np.random.uniform(-1, 1, size=(n_samples,1))
gp_length_scale=.4


with pm.Model() as model:
    # Encapsulate data for use in our model
    x_sampled_c = pm.Data("x_sampled", x_sampled)
    t_sampled_c = pm.Data("t_sampled", t_sampled)
    z_observations_c = pm.Data("z_observations", z_observations)

    # Define a gaussian process to represent the coefficient
    # The mean function will default to 0
    cov_func = pm.gp.cov.ExpQuad(1, ls=gp_length_scale)
    gp = pm.gp.Latent(cov_func=cov_func)

    # Place a GP prior over coefficient
    Cx = gp.prior("Cx", X=t_sampled_c)

    # Prior for the intercept
    b = pm.Normal("b", mu=0, sd=1) 

    # Prior for noise
    noise = pm.Gamma("noise", alpha=2, beta=1)

    # Assume a linear functional form defined by sampled coefficient
    z = pm.Deterministic("z", Cx*x_sampled_c + b)

    # assume that observations are sampled with gaussian noise
    z_measured = pm.Normal("z_measured", mu=z, sd=noise, observed=z_observations_c)

The stack trace is:

---------------------------------------------------------------------------
TypeError                                 Traceback (most recent call last)
/usr/local/anaconda3/lib/python3.7/site-packages/pymc3/gp/util.py in infer_shape(X, n_points)
     13         try:
---> 14             n_points = np.int(X.shape[0])
     15         except TypeError:

TypeError: int() argument must be a string, a bytes-like object or a number, not 'TensorVariable'

During handling of the above exception, another exception occurred:

TypeError                                 Traceback (most recent call last)
<ipython-input-30-aab14f998f0f> in <module>
     18 
     19     # Place a GP prior over coefficient
---> 20     Cx = gp.prior("Cx", X=t_sampled_c)
     21 
     22     # Prior for the intercept

/usr/local/anaconda3/lib/python3.7/site-packages/pymc3/gp/gp.py in prior(self, name, X, reparameterize, **kwargs)
    143         """
    144 
--> 145         f = self._build_prior(name, X, reparameterize, **kwargs)
    146         self.X = X
    147         self.f = f

/usr/local/anaconda3/lib/python3.7/site-packages/pymc3/gp/gp.py in _build_prior(self, name, X, reparameterize, **kwargs)
    110         mu = self.mean_func(X)
    111         cov = stabilize(self.cov_func(X))
--> 112         shape = infer_shape(X, kwargs.pop("shape", None))
    113         if reparameterize:
    114             v = pm.Normal(name + "_rotated_", mu=0.0, sigma=1.0, shape=shape, **kwargs)

/usr/local/anaconda3/lib/python3.7/site-packages/pymc3/gp/util.py in infer_shape(X, n_points)
     14             n_points = np.int(X.shape[0])
     15         except TypeError:
---> 16             raise TypeError("Cannot infer 'shape', provide as an argument")
     17     return n_points
     18 

TypeError: Cannot infer 'shape', provide as an argument

I’ve tried including a shape parameter in the GP prior:

n_samples = 40
x_sampled = np.random.uniform(-1, 1, size=(n_samples,1))
t_sampled = np.random.uniform(-1, 1, size=(1, n_samples))
z_observations = np.random.uniform(-1, 1, size=(n_samples,1))
gp_length_scale=.4


with pm.Model() as model:
    # Encapsulate data for use in our model
    x_sampled_c = pm.Data("x_sampled", x_sampled)
    t_sampled_c = pm.Data("t_sampled", t_sampled)
    z_observations_c = pm.Data("z_observations", z_observations)

    # Define a gaussian process to represent the coefficient
    # The mean function will default to 0
    cov_func = pm.gp.cov.ExpQuad(1, ls=gp_length_scale)
    gp = pm.gp.Latent(cov_func=cov_func)

    # Place a GP prior over coefficient
    Cx = gp.prior("Cx", X=t_sampled_c, shape=t_sampled.shape)

    # Prior for the intercept
    b = pm.Normal("b", mu=0, sd=1) 

    # Prior for noise
    noise = pm.Gamma("noise", alpha=2, beta=1)

    # Assume a linear functional form defined by sampled coefficient
    z = pm.Deterministic("z", Cx*x_sampled_c + b)

    # assume that observations are sampled with gaussian noise
    z_measured = pm.Normal("z_measured", mu=z, sd=noise, observed=z_observations_c)

but this seems to just kick the can down the road to the next point I need to use the GP:

---------------------------------------------------------------------------
ValueError                                Traceback (most recent call last)
<ipython-input-33-61d6bbd10b21> in <module>
     18 
     19     # Place a GP prior over coefficient
---> 20     Cx = gp.prior("Cx", X=t_sampled_c, shape=t_sampled.shape)
     21 
     22     # Prior for the intercept

/usr/local/anaconda3/lib/python3.7/site-packages/pymc3/gp/gp.py in prior(self, name, X, reparameterize, **kwargs)
    143         """
    144 
--> 145         f = self._build_prior(name, X, reparameterize, **kwargs)
    146         self.X = X
    147         self.f = f

/usr/local/anaconda3/lib/python3.7/site-packages/pymc3/gp/gp.py in _build_prior(self, name, X, reparameterize, **kwargs)
    113         if reparameterize:
    114             v = pm.Normal(name + "_rotated_", mu=0.0, sigma=1.0, shape=shape, **kwargs)
--> 115             f = pm.Deterministic(name, mu + cholesky(cov).dot(v))
    116         else:
    117             f = pm.MvNormal(name, mu=mu, cov=cov, shape=shape, **kwargs)

/usr/local/anaconda3/lib/python3.7/site-packages/theano/tensor/var.py in __add__(self, other)
    126     def __add__(self, other):
    127         try:
--> 128             return theano.tensor.basic.add(self, other)
    129         # We should catch the minimum number of exception here.
    130         # Otherwise this will convert error when Theano flags

/usr/local/anaconda3/lib/python3.7/site-packages/theano/gof/op.py in __call__(self, *inputs, **kwargs)
    672                 thunk.outputs = [storage_map[v] for v in node.outputs]
    673 
--> 674                 required = thunk()
    675                 assert not required  # We provided all inputs
    676 

/usr/local/anaconda3/lib/python3.7/site-packages/theano/gof/op.py in rval()
    860 
    861         def rval():
--> 862             thunk()
    863             for o in node.outputs:
    864                 compute_map[o][0] = True

/usr/local/anaconda3/lib/python3.7/site-packages/theano/gof/cc.py in __call__(self)
   1737                 print(self.error_storage, file=sys.stderr)
   1738                 raise
-> 1739             reraise(exc_type, exc_value, exc_trace)
   1740 
   1741 

/usr/local/anaconda3/lib/python3.7/site-packages/six.py in reraise(tp, value, tb)
    701             if value.__traceback__ is not tb:
    702                 raise value.with_traceback(tb)
--> 703             raise value
    704         finally:
    705             value = None

ValueError: Input dimension mis-match. (input[0].shape[1] = 1, input[1].shape[1] = 40)

Thing is, it works fine without the data container:

n_samples = 40
x_sampled = np.random.uniform(-1, 1, size=(n_samples,1))
t_sampled = np.random.uniform(-1, 1, size=(1,n_samples))
z_observations = np.random.uniform(-1, 1, size=(n_samples,1))
gp_length_scale=.4


with pm.Model() as model:
    # Encapsulate data for use in our model
    x_sampled_c = x_sampled #pm.Data("x_sampled", x_sampled)
    t_sampled_c = t_sampled #pm.Data("t_sampled", t_sampled)
    z_observations_c = z_observations #pm.Data("z_observations", z_observations)

    # Define a gaussian process to represent the coefficient
    # The mean function will default to 0
    cov_func = pm.gp.cov.ExpQuad(1, ls=gp_length_scale)
    gp = pm.gp.Latent(cov_func=cov_func)

    # Place a GP prior over coefficient
    Cx = gp.prior("Cx", X=t_sampled_c)

    # Prior for the intercept
    b = pm.Normal("b", mu=0, sd=1) 

    # Prior for noise
    noise = pm.Gamma("noise", alpha=2, beta=1)

    # Assume a linear functional form defined by sampled coefficient
    z = pm.Deterministic("z", Cx*x_sampled_c + b)

    # assume that observations are sampled with gaussian noise
    z_measured = pm.Normal("z_measured", mu=z, sd=noise, observed=z_observations_c)

I just want to be able to predict new points later on…

Am I missing a data alignment/transform somewhere, or is this a bug?

My system:

Python 3.7.6
numpy 1.19.2
theano 1.0.4
pymc3 3.8

I think that specifying the shape in the GP is the right way to go but I think you might not use the correct shape there. Shouldn’t that just be n_samples?

We have a very broad open issue for pm.Data within GP: Data container not compatible with GP models · Issue #3500 · pymc-devs/pymc3 · GitHub

This might be related (unless it is a simple shape misspecification as @twiecki mentioned above).