# 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  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:

 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)