# Source code for pyro.contrib.gp.models.gplvm

# Copyright (c) 2017-2019 Uber Technologies, Inc.

import pyro.distributions as dist
from pyro.contrib.gp.parameterized import Parameterized
from pyro.nn.module import PyroSample, pyro_method

[docs]class GPLVM(Parameterized):
"""
Gaussian Process Latent Variable Model (GPLVM) model.

GPLVM is a Gaussian Process model with its train input data is a latent variable.
This model is useful for dimensional reduction of high dimensional data. Assume the
mapping from low dimensional latent variable to is a Gaussian Process instance.
Then the high dimensional data will play the role of train output y and our
target is to learn latent inputs which best explain y. For the purpose of
dimensional reduction, latent inputs should have lower dimensions than y.

We follows reference [1] to put a unit Gaussian prior to the input and approximate
its posterior by a multivariate normal distribution with two variational
parameters: X_loc and X_scale_tril.

For example, we can do dimensional reduction on Iris dataset as follows:

>>> # With y as the 2D Iris data of shape 150x4 and we want to reduce its dimension
>>> # to a tensor X of shape 150x2, we will use GPLVM.

.. doctest::
:hide:

>>> # Simulating iris data.
>>> y = torch.stack([dist.Normal(4.8, 0.1).sample((150,)),
...                  dist.Normal(3.2, 0.3).sample((150,)),
...                  dist.Normal(1.5, 0.4).sample((150,)),
...                  dist.Exponential(0.5).sample((150,))])

>>> # First, define the initial values for X parameter:
>>> X_init = torch.zeros(150, 2)
>>> # Then, define a Gaussian Process model with input X_init and output y:
>>> kernel = gp.kernels.RBF(input_dim=2, lengthscale=torch.ones(2))
>>> Xu = torch.zeros(20, 2)  # initial inducing inputs of sparse model
>>> gpmodule = gp.models.SparseGPRegression(X_init, y, kernel, Xu)
>>> # Finally, wrap gpmodule by GPLVM, optimize, and get the "learned" mean of X:
>>> gplvm = gp.models.GPLVM(gpmodule)
>>> gp.util.train(gplvm)  # doctest: +SKIP
>>> X = gplvm.X

Reference:

[1] Bayesian Gaussian Process Latent Variable Model
Michalis K. Titsias, Neil D. Lawrence

:param ~pyro.contrib.gp.models.model.GPModel base_model: A Pyro Gaussian Process
model object. Note that base_model.X will be the initial value for the
variational parameter X_loc.
"""
def __init__(self, base_model):
super().__init__()
if base_model.X.dim() != 2:
raise ValueError("GPLVM model only works with 2D latent X, but got "
"X.dim() = {}.".format(base_model.X.dim()))
self.base_model = base_model

self.X = PyroSample(dist.Normal(base_model.X.new_zeros(base_model.X.shape), 1.).to_event())
self.autoguide("X", dist.Normal)
self.X_loc.data = base_model.X

[docs]    @pyro_method
def model(self):
self.mode = "model"
# X is sampled from prior will be put into base_model
self.base_model.set_data(self.X, self.base_model.y)
self.base_model.model()

[docs]    @pyro_method
def guide(self):
self.mode = "guide"
# X is sampled from guide will be put into base_model
self.base_model.set_data(self.X, self.base_model.y)
self.base_model.guide()

[docs]    def forward(self, **kwargs):
"""
Forward method has the same signal as its base_model. Note that the train
input data of base_model is sampled from GPLVM.
"""
self.mode = "guide"
self.base_model.set_data(self.X, self.base_model.y)
return self.base_model(**kwargs)