2013-12-12 14:18:18 +00:00
# ## Copyright (c) 2013, GPy authors (see AUTHORS.txt).
# Licensed under the BSD 3-clause license (see LICENSE.txt)
2013-04-11 15:02:26 +01:00
2014-03-04 17:32:46 +00:00
import numpy as np
2014-05-21 16:32:06 +01:00
import itertools , logging
2014-03-04 17:32:46 +00:00
from . . kern import Kern
from . . core . parameterization . variational import NormalPosterior , NormalPrior
2014-03-10 08:17:02 +00:00
from . . core . parameterization import Param , Parameterized
2014-05-20 14:49:20 +01:00
from . . core . parameterization . observable_array import ObsAr
2014-10-31 17:09:14 +00:00
from . . inference . latent_function_inference . var_dtc import VarDTC
2014-05-20 14:49:20 +01:00
from . . inference . latent_function_inference import InferenceMethodList
2014-03-10 08:17:02 +00:00
from . . likelihoods import Gaussian
2014-05-16 11:21:08 +01:00
from . . util . initialization import initialize_latent
from . . core . sparse_gp import SparseGP , GP
2014-10-31 17:09:14 +00:00
from GPy . core . parameterization . variational import VariationalPosterior
2014-11-03 15:04:12 +00:00
from GPy . models . bayesian_gplvm_minibatch import BayesianGPLVMMiniBatch
from GPy . models . sparse_gp_minibatch import SparseGPMiniBatch
2013-04-11 15:02:26 +01:00
2014-11-03 15:04:12 +00:00
class MRD ( BayesianGPLVMMiniBatch ) :
2014-02-19 15:32:16 +00:00
"""
2014-10-31 17:09:14 +00:00
! WARNING : This is bleeding edge code and still in development .
2014-05-20 14:49:20 +01:00
Functionality may change fundamentally during development !
2014-03-13 13:13:15 +00:00
Apply MRD to all given datasets Y in Ylist .
2014-02-19 15:32:16 +00:00
Y_i in [ n x p_i ]
2014-03-13 13:13:15 +00:00
2014-05-20 14:49:20 +01:00
If Ylist is a dictionary , the keys of the dictionary are the names , and the
2014-10-31 17:09:14 +00:00
values are the different datasets to compare .
2014-05-20 14:49:20 +01:00
2014-03-13 13:13:15 +00:00
The samples n in the datasets need
2014-02-19 15:32:16 +00:00
to match up , whereas the dimensionality p_d can differ .
2014-03-13 13:13:15 +00:00
2014-02-19 15:32:16 +00:00
: param [ array - like ] Ylist : List of datasets to apply MRD on
2014-03-04 17:32:46 +00:00
: param input_dim : latent dimensionality
: type input_dim : int
: param array - like X : mean of starting latent space q in [ n x q ]
: param array - like X_variance : variance of starting latent space q in [ n x q ]
: param initx : initialisation method for the latent space :
* ' concat ' - PCA on concatenation of all datasets
* ' single ' - Concatenation of PCA on datasets , respectively
* ' random ' - Random draw from a Normal ( 0 , 1 )
: type initx : [ ' concat ' | ' single ' | ' random ' ]
: param initz : initialisation method for inducing inputs
: type initz : ' permute ' | ' random '
: param num_inducing : number of inducing inputs to use
: param Z : initial inducing inputs
: param kernel : list of kernels or kernel to copy for each output
2014-05-16 11:21:08 +01:00
: type kernel : [ GPy . kernels . kernels ] | GPy . kernels . kernels | None ( default )
2014-10-31 17:09:14 +00:00
: param : class : ` ~ GPy . inference . latent_function_inference inference_method :
2014-05-16 11:21:08 +01:00
InferenceMethodList of inferences , or one inference method for all
: param : class : ` ~ GPy . likelihoodss . likelihoods . likelihoods ` likelihoods : the likelihoods to use
2014-03-04 17:32:46 +00:00
: param str name : the name of this model
2014-03-10 08:17:02 +00:00
: param [ str ] Ynames : the names for the datasets given , must be of equal length as Ylist or None
2014-10-31 17:09:14 +00:00
: param bool | Norm normalizer : How to normalize the data ?
: param bool stochastic : Should this model be using stochastic gradient descent over the dimensions ?
: param bool | [ bool ] batchsize : either one batchsize for all , or one batchsize per dataset .
2014-02-19 15:32:16 +00:00
"""
2014-03-13 13:13:15 +00:00
def __init__ ( self , Ylist , input_dim , X = None , X_variance = None ,
2014-03-04 17:32:46 +00:00
initx = ' PCA ' , initz = ' permute ' ,
2014-03-13 13:13:15 +00:00
num_inducing = 10 , Z = None , kernel = None ,
2014-10-31 17:09:14 +00:00
inference_method = None , likelihoods = None , name = ' mrd ' ,
Ynames = None , normalizer = False , stochastic = False , batchsize = 10 ) :
2014-03-13 13:13:15 +00:00
2014-05-22 11:39:04 +01:00
self . logger = logging . getLogger ( self . __class__ . __name__ )
2014-03-24 13:33:16 +00:00
self . input_dim = input_dim
self . num_inducing = num_inducing
2014-05-20 14:49:20 +01:00
if isinstance ( Ylist , dict ) :
Ynames , Ylist = zip ( * Ylist . items ( ) )
2014-05-21 16:32:06 +01:00
self . logger . debug ( " creating observable arrays " )
2014-05-20 14:49:20 +01:00
self . Ylist = [ ObsAr ( Y ) for Y in Ylist ]
if Ynames is None :
2014-05-21 16:32:06 +01:00
self . logger . debug ( " creating Ynames " )
2014-05-20 14:49:20 +01:00
Ynames = [ ' Y {} ' . format ( i ) for i in range ( len ( Ylist ) ) ]
self . names = Ynames
assert len ( self . names ) == len ( self . Ylist ) , " one name per dataset, or None if Ylist is a dict "
if inference_method is None :
2014-10-31 17:09:14 +00:00
self . inference_method = InferenceMethodList ( [ VarDTC ( ) for _ in xrange ( len ( self . Ylist ) ) ] )
2014-05-20 14:49:20 +01:00
else :
2014-10-31 17:09:14 +00:00
assert isinstance ( inference_method , InferenceMethodList ) , " please provide one inference method per Y in the list and provide it as InferenceMethodList, inference_method given: {} " . format ( inference_method )
2014-05-20 14:49:20 +01:00
self . inference_method = inference_method
if X is None :
X , fracs = self . _init_X ( initx , Ylist )
else :
fracs = [ X . var ( 0 ) ] * len ( Ylist )
2014-10-31 17:09:14 +00:00
2014-11-03 15:04:12 +00:00
Z = self . _init_Z ( initz , X )
self . Z = Param ( ' inducing inputs ' , Z )
2014-03-24 13:33:16 +00:00
self . num_inducing = self . Z . shape [ 0 ] # ensure M==N if M>N
2014-03-04 17:32:46 +00:00
# sort out the kernels
2014-05-21 16:32:06 +01:00
self . logger . info ( " building kernels " )
2014-03-04 17:32:46 +00:00
if kernel is None :
from . . kern import RBF
2014-11-12 15:08:38 +00:00
kernels = [ RBF ( input_dim , ARD = 1 , lengthscale = 1. / fracs [ i ] ) for i in range ( len ( Ylist ) ) ]
2014-03-04 17:32:46 +00:00
elif isinstance ( kernel , Kern ) :
2014-05-22 11:39:04 +01:00
kernels = [ ]
2014-03-31 12:45:09 +01:00
for i in range ( len ( Ylist ) ) :
k = kernel . copy ( )
2014-05-22 11:39:04 +01:00
kernels . append ( k )
2014-03-04 17:32:46 +00:00
else :
assert len ( kernel ) == len ( Ylist ) , " need one kernel per output "
assert all ( [ isinstance ( k , Kern ) for k in kernel ] ) , " invalid kernel object detected! "
2014-05-22 11:39:04 +01:00
kernels = kernel
2014-03-13 13:13:15 +00:00
2014-03-04 17:32:46 +00:00
self . variational_prior = NormalPrior ( )
2014-11-03 15:04:12 +00:00
#self.X = NormalPosterior(X, X_variance)
2014-03-13 13:13:15 +00:00
2014-05-16 11:21:08 +01:00
if likelihoods is None :
2014-05-22 11:39:04 +01:00
likelihoods = [ Gaussian ( name = ' Gaussian_noise ' . format ( i ) ) for i in range ( len ( Ylist ) ) ]
else : likelihoods = likelihoods
2014-03-13 13:13:15 +00:00
2014-05-21 16:32:06 +01:00
self . logger . info ( " adding X and Z " )
2014-10-31 17:09:14 +00:00
super ( MRD , self ) . __init__ ( Y , input_dim , X = X , X_variance = X_variance , num_inducing = num_inducing ,
Z = self . Z , kernel = None , inference_method = self . inference_method , likelihood = Gaussian ( ) ,
2014-11-03 15:04:12 +00:00
name = ' manifold relevance determination ' , normalizer = None ,
2014-10-31 17:09:14 +00:00
missing_data = False , stochastic = False , batchsize = 1 )
self . _log_marginal_likelihood = 0
self . unlink_parameter ( self . likelihood )
self . unlink_parameter ( self . kern )
2014-11-03 15:04:12 +00:00
del self . kern
del self . likelihood
2014-03-13 13:13:15 +00:00
2014-05-16 11:21:08 +01:00
self . num_data = Ylist [ 0 ] . shape [ 0 ]
2014-10-31 17:09:14 +00:00
if isinstance ( batchsize , int ) :
batchsize = itertools . repeat ( batchsize )
2014-05-16 11:21:08 +01:00
2014-11-03 15:04:12 +00:00
self . bgplvms = [ ]
2014-10-31 17:09:14 +00:00
for i , n , k , l , Y , im , bs in itertools . izip ( itertools . count ( ) , Ynames , kernels , likelihoods , Ylist , self . inference_method , batchsize ) :
2014-05-16 11:21:08 +01:00
assert Y . shape [ 0 ] == self . num_data , " All datasets need to share the number of datapoints, and those have to correspond to one another "
2014-10-31 17:09:14 +00:00
md = np . isnan ( Y ) . any ( )
2014-11-12 11:34:44 +00:00
spgp = BayesianGPLVMMiniBatch ( Y , input_dim , X , X_variance ,
Z = Z , kernel = k , likelihood = l ,
inference_method = im , name = n ,
normalizer = normalizer ,
missing_data = md ,
stochastic = stochastic ,
batchsize = bs )
spgp . kl_factr = 1. / len ( Ynames )
2014-10-31 17:09:14 +00:00
spgp . unlink_parameter ( spgp . Z )
2014-11-12 11:34:44 +00:00
spgp . unlink_parameter ( spgp . X )
2014-11-03 15:04:12 +00:00
del spgp . Z
del spgp . X
2014-10-31 17:09:14 +00:00
spgp . Z = self . Z
2014-11-03 15:04:12 +00:00
spgp . X = self . X
2014-10-31 17:09:14 +00:00
self . link_parameter ( spgp , i + 2 )
2014-11-03 15:04:12 +00:00
self . bgplvms . append ( spgp )
2014-05-16 11:21:08 +01:00
self . posterior = None
2014-05-21 16:32:06 +01:00
self . logger . info ( " init done " )
2014-03-13 12:29:35 +00:00
2014-03-04 17:32:46 +00:00
def parameters_changed ( self ) :
2014-03-10 08:17:02 +00:00
self . _log_marginal_likelihood = 0
2014-03-31 12:45:09 +01:00
self . Z . gradient [ : ] = 0.
self . X . gradient [ : ] = 0.
2014-11-03 15:04:12 +00:00
for b , i in itertools . izip ( self . bgplvms , self . inference_method ) :
self . _log_marginal_likelihood + = b . _log_marginal_likelihood
2014-05-21 16:32:06 +01:00
self . logger . info ( ' working on im < {} > ' . format ( hex ( id ( i ) ) ) )
2014-10-31 17:09:14 +00:00
self . Z . gradient [ : ] + = b . full_values [ ' Zgrad ' ]
2014-11-12 11:34:44 +00:00
grad_dict = b . full_values
2014-10-31 17:09:14 +00:00
2014-12-03 08:35:41 +00:00
if self . has_uncertain_inputs ( ) :
self . X . mean . gradient + = grad_dict [ ' meangrad ' ]
self . X . variance . gradient + = grad_dict [ ' vargrad ' ]
else :
self . X . gradient + = grad_dict [ ' Xgrad ' ]
2014-10-31 17:09:14 +00:00
2014-12-03 08:35:41 +00:00
if self . has_uncertain_inputs ( ) :
2014-10-31 17:09:14 +00:00
# update for the KL divergence
self . variational_prior . update_gradients_KL ( self . X )
self . _log_marginal_likelihood - = self . variational_prior . KL_divergence ( self . X )
2014-11-03 15:04:12 +00:00
pass
2014-03-10 08:17:02 +00:00
def log_likelihood ( self ) :
return self . _log_marginal_likelihood
2014-03-04 17:32:46 +00:00
2014-03-10 08:17:02 +00:00
def _init_X ( self , init = ' PCA ' , Ylist = None ) :
if Ylist is None :
Ylist = self . Ylist
2014-03-04 17:32:46 +00:00
if init in " PCA_concat " :
2014-03-24 13:33:16 +00:00
X , fracs = initialize_latent ( ' PCA ' , self . input_dim , np . hstack ( Ylist ) )
2014-05-20 14:49:20 +01:00
fracs = [ fracs ] * len ( Ylist )
2014-03-04 17:32:46 +00:00
elif init in " PCA_single " :
X = np . zeros ( ( Ylist [ 0 ] . shape [ 0 ] , self . input_dim ) )
2014-03-24 13:33:16 +00:00
fracs = [ ]
2014-03-04 17:32:46 +00:00
for qs , Y in itertools . izip ( np . array_split ( np . arange ( self . input_dim ) , len ( Ylist ) ) , Ylist ) :
2014-03-24 13:33:16 +00:00
x , frcs = initialize_latent ( ' PCA ' , len ( qs ) , Y )
X [ : , qs ] = x
fracs . append ( frcs )
2014-03-04 17:32:46 +00:00
else : # init == 'random':
X = np . random . randn ( Ylist [ 0 ] . shape [ 0 ] , self . input_dim )
2014-03-24 13:33:16 +00:00
fracs = X . var ( 0 )
2014-05-20 14:49:20 +01:00
fracs = [ fracs ] * len ( Ylist )
2014-03-31 12:45:09 +01:00
X - = X . mean ( )
X / = X . std ( )
2014-03-24 13:33:16 +00:00
return X , fracs
2014-03-04 17:32:46 +00:00
def _init_Z ( self , init = " permute " , X = None ) :
if X is None :
X = self . X
if init in " permute " :
Z = np . random . permutation ( X . copy ( ) ) [ : self . num_inducing ]
elif init in " random " :
Z = np . random . randn ( self . num_inducing , self . input_dim ) * X . var ( )
return Z
2014-02-19 15:32:16 +00:00
2013-06-28 11:00:42 +01:00
def _handle_plotting ( self , fignum , axes , plotf , sharex = False , sharey = False ) :
2014-05-21 16:32:06 +01:00
import matplotlib . pyplot as plt
2013-06-04 18:26:16 +01:00
if axes is None :
2014-05-21 16:32:06 +01:00
fig = plt . figure ( num = fignum )
2013-06-28 11:00:42 +01:00
sharex_ax = None
sharey_ax = None
2014-05-16 15:12:19 +01:00
plots = [ ]
2013-04-17 15:45:20 +01:00
for i , g in enumerate ( self . bgplvms ) :
2013-06-28 11:00:42 +01:00
try :
if sharex :
sharex_ax = ax # @UndefinedVariable
sharex = False # dont set twice
if sharey :
sharey_ax = ax # @UndefinedVariable
sharey = False # dont set twice
except :
pass
2013-06-04 18:26:16 +01:00
if axes is None :
2013-06-28 11:00:42 +01:00
ax = fig . add_subplot ( 1 , len ( self . bgplvms ) , i + 1 , sharex = sharex_ax , sharey = sharey_ax )
2014-11-12 11:34:44 +00:00
elif isinstance ( axes , ( tuple , list , np . ndarray ) ) :
2013-06-05 11:21:02 +01:00
ax = axes [ i ]
2013-06-04 18:25:28 +01:00
else :
2013-06-05 11:17:15 +01:00
raise ValueError ( " Need one axes per latent dimension input_dim " )
2014-05-16 15:12:19 +01:00
plots . append ( plotf ( i , g , ax ) )
2013-07-29 15:27:14 +01:00
if sharey_ax is not None :
2014-05-21 16:32:06 +01:00
plt . setp ( ax . get_yticklabels ( ) , visible = False )
plt . draw ( )
2013-06-04 18:26:16 +01:00
if axes is None :
2014-05-16 15:12:19 +01:00
try :
fig . tight_layout ( )
except :
pass
return plots
2013-04-17 15:45:20 +01:00
2014-05-16 11:21:08 +01:00
def predict ( self , Xnew , full_cov = False , Y_metadata = None , kern = None , Yindex = 0 ) :
"""
Prediction for data set Yindex [ default = 0 ] .
This predicts the output mean and variance for the dataset given in Ylist [ Yindex ]
"""
2014-11-03 15:04:12 +00:00
b = self . bgplvms [ Yindex ]
2014-10-31 17:09:14 +00:00
self . posterior = b . posterior
self . kern = b . kern
self . likelihood = b . likelihood
2014-05-16 11:21:08 +01:00
return super ( MRD , self ) . predict ( Xnew , full_cov , Y_metadata , kern )
#===============================================================================
# TODO: Predict! Maybe even change to several bgplvms, which share an X?
#===============================================================================
# def plot_predict(self, fignum=None, ax=None, sharex=False, sharey=False, **kwargs):
# fig = self._handle_plotting(fignum,
# ax,
# lambda i, g, ax: ax.imshow(g.predict(g.X)[0], **kwargs),
# sharex=sharex, sharey=sharey)
# return fig
2013-04-11 15:02:26 +01:00
2013-06-28 11:00:42 +01:00
def plot_scales ( self , fignum = None , ax = None , titles = None , sharex = False , sharey = True , * args , * * kwargs ) :
"""
2013-09-20 13:38:20 +01:00
TODO : Explain other parameters
: param titles : titles for axes of datasets
2013-06-28 11:00:42 +01:00
"""
if titles is None :
titles = [ r ' $ {} $ ' . format ( name ) for name in self . names ]
2014-05-16 15:12:19 +01:00
ymax = reduce ( max , [ np . ceil ( max ( g . kern . input_sensitivity ( ) ) ) for g in self . bgplvms ] )
2013-06-28 11:00:42 +01:00
def plotf ( i , g , ax ) :
2014-11-12 11:34:44 +00:00
#ax.set_ylim([0,ymax])
2014-05-16 15:12:19 +01:00
return g . kern . plot_ARD ( ax = ax , title = titles [ i ] , * args , * * kwargs )
2013-06-28 11:00:42 +01:00
fig = self . _handle_plotting ( fignum , ax , plotf , sharex = sharex , sharey = sharey )
2013-04-11 18:44:18 +01:00
return fig
2014-05-16 11:21:08 +01:00
def plot_latent ( self , labels = None , which_indices = None ,
resolution = 50 , ax = None , marker = ' o ' , s = 40 ,
fignum = None , plot_inducing = True , legend = True ,
2014-10-31 17:09:14 +00:00
plot_limits = None ,
2014-05-16 15:12:19 +01:00
aspect = ' auto ' , updates = False , predict_kwargs = { } , imshow_kwargs = { } ) :
2014-05-16 11:21:08 +01:00
"""
2014-05-16 15:12:19 +01:00
see plotting . matplot_dep . dim_reduction_plots . plot_latent
if predict_kwargs is None , will plot latent spaces for 0 th dataset ( and kernel ) , otherwise give
predict_kwargs = dict ( Yindex = ' index ' ) for plotting only the latent space of dataset with ' index ' .
2014-05-16 11:21:08 +01:00
"""
import sys
assert " matplotlib " in sys . modules , " matplotlib package has not been imported. "
2014-05-22 11:39:04 +01:00
from matplotlib import pyplot as plt
2014-05-16 11:21:08 +01:00
from . . plotting . matplot_dep import dim_reduction_plots
2014-05-16 15:12:19 +01:00
if " Yindex " not in predict_kwargs :
predict_kwargs [ ' Yindex ' ] = 0
2014-11-03 15:04:12 +00:00
Yindex = predict_kwargs [ ' Yindex ' ]
2014-05-16 15:12:19 +01:00
if ax is None :
2014-05-21 16:32:06 +01:00
fig = plt . figure ( num = fignum )
2014-05-16 15:12:19 +01:00
ax = fig . add_subplot ( 111 )
else :
fig = ax . figure
2014-11-03 15:04:12 +00:00
self . kern = self . bgplvms [ Yindex ] . kern
self . likelihood = self . bgplvms [ Yindex ] . likelihood
2014-05-16 15:12:19 +01:00
plot = dim_reduction_plots . plot_latent ( self , labels , which_indices ,
resolution , ax , marker , s ,
fignum , plot_inducing , legend ,
plot_limits , aspect , updates , predict_kwargs , imshow_kwargs )
2014-11-03 15:04:12 +00:00
ax . set_title ( self . bgplvms [ Yindex ] . name )
2014-05-16 15:12:19 +01:00
try :
fig . tight_layout ( )
except :
pass
2014-05-16 11:21:08 +01:00
2014-05-16 15:12:19 +01:00
return plot
2013-04-12 13:30:28 +01:00
2014-05-16 11:21:08 +01:00
def __getstate__ ( self ) :
2014-05-22 11:39:04 +01:00
state = super ( MRD , self ) . __getstate__ ( )
2014-11-03 15:04:12 +00:00
if state . has_key ( ' kern ' ) :
del state [ ' kern ' ]
if state . has_key ( ' likelihood ' ) :
del state [ ' likelihood ' ]
2014-05-16 11:21:08 +01:00
return state
def __setstate__ ( self , state ) :
# TODO:
super ( MRD , self ) . __setstate__ ( state )
2014-05-22 11:39:04 +01:00
self . kern = self . bgplvms [ 0 ] . kern
self . likelihood = self . bgplvms [ 0 ] . likelihood
2014-05-16 11:21:08 +01:00
self . parameters_changed ( )