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
2016-03-01 10:02:02 +00:00
from . . core . parameterization . variational import NormalPrior
2015-10-15 14:59:57 +01:00
from . . core . parameterization import Param
from paramz 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
2016-03-01 10:02:02 +00:00
from . . models . bayesian_gplvm_minibatch import BayesianGPLVMMiniBatch
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 ]
2015-03-06 13:06:42 +00:00
#The next line is a fix for Python 3. It replicates the python 2 behaviour from the above comprehension
Y = Ylist [ - 1 ]
2014-05-20 14:49:20 +01:00
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 :
2015-03-02 20:13:21 +00:00
self . inference_method = InferenceMethodList ( [ VarDTC ( ) for _ in range ( 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
2015-03-06 14:48:19 +00:00
for i , n , k , l , Y , im , bs in zip ( 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.
2015-03-06 14:48:19 +00:00
for b , i in zip ( self . bgplvms , self . inference_method ) :
2014-11-03 15:04:12 +00:00
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 ) ) ) )
2015-09-24 13:44:28 +01:00
self . Z . gradient [ : ] + = b . _Zgrad # b.Z.gradient # full_values['Zgrad']
2015-09-04 11:07:39 +01: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 ( ) :
2015-09-04 16:43:26 +01:00
self . X . gradient + = b . _Xgrad
2014-12-03 08:35:41 +00:00
else :
2015-09-04 16:43:26 +01:00
self . X . gradient + = b . _Xgrad
2014-10-31 17:09:14 +00:00
2015-09-04 16:43:26 +01:00
#if self.has_uncertain_inputs():
# # update for the KL divergence
# self.variational_prior.update_gradients_KL(self.X)
# self._log_marginal_likelihood -= self.variational_prior.KL_divergence(self.X)
# 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 = [ ]
2015-03-06 14:48:19 +00:00
for qs , Y in zip ( 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
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
2016-03-01 10:02:02 +00:00
def plot_scales ( self , titles = None , fig_kwargs = dict ( figsize = None , tight_layout = True ) , * * kwargs ) :
2013-06-28 11:00:42 +01:00
"""
2016-03-01 10:02:02 +00:00
Plot input sensitivity for all datasets , to see which input dimensions are
significant for which dataset .
2013-09-20 13:38:20 +01:00
: param titles : titles for axes of datasets
2016-03-01 10:02:02 +00:00
kwargs go into plot_ARD for each kernel .
2013-06-28 11:00:42 +01:00
"""
2016-03-01 10:02:02 +00:00
from . . plotting import plotting_library as pl
2013-06-28 11:00:42 +01:00
if titles is None :
titles = [ r ' $ {} $ ' . format ( name ) for name in self . names ]
2016-03-01 10:02:02 +00:00
M = len ( self . bgplvms )
fig = pl ( ) . figure ( rows = 1 , cols = M , * * fig_kwargs )
plots = { }
for c in range ( M ) :
canvas = self . bgplvms [ c ] . kern . plot_ARD ( title = titles [ c ] , figure = fig , col = c + 1 , * * kwargs )
plots [ titles [ c ] ] = canvas
pl ( ) . show_canvas ( canvas )
return plots
2013-04-11 18:44:18 +01:00
2014-05-16 11:21:08 +01:00
def plot_latent ( self , labels = None , which_indices = None ,
2016-03-01 10:02:02 +00:00
resolution = 60 , legend = True ,
2014-10-31 17:09:14 +00:00
plot_limits = None ,
2016-03-01 10:02:02 +00:00
updates = False ,
kern = None , marker = ' <>^vsd ' ,
num_samples = 1000 , projection = ' 2d ' ,
predict_kwargs = { } ,
scatter_kwargs = None , * * 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
"""
2016-03-01 10:02:02 +00:00
from . . plotting . gpy_plot . latent_plots import plot_latent
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 ' ]
2016-03-01 10:02:02 +00:00
2014-11-03 15:04:12 +00:00
self . kern = self . bgplvms [ Yindex ] . kern
self . likelihood = self . bgplvms [ Yindex ] . likelihood
2016-03-01 10:02:02 +00:00
return plot_latent ( self , labels , which_indices , resolution , legend , plot_limits , updates , kern , marker , num_samples , projection , scatter_kwargs )
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__ ( )
2015-03-06 17:07:35 +00:00
if ' kern ' in state :
2014-11-03 15:04:12 +00:00
del state [ ' kern ' ]
2015-03-06 17:07:35 +00:00
if ' likelihood ' in state :
2014-11-03 15:04:12 +00:00
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
2015-03-02 20:13:21 +00:00
self . parameters_changed ( )
2015-10-01 13:07:57 +01:00
def factorize_space ( self , threshold = 0.005 , printOut = False , views = None ) :
"""
Given a trained MRD model , this function looks at the optimized ARD weights ( lengthscales )
and decides which part of the latent space is shared across views or private , according to a threshold .
The threshold is applied after all weights are normalized so that the maximum value is 1.
"""
M = len ( self . bgplvms )
if views is None :
# There are some small modifications needed to make this work for M > 2 (currently the code
# takes account of this, but it's not right there)
if M is not 2 :
raise NotImplementedError ( " Not implemented for M > 2 " )
obsMod = [ 0 ]
infMod = 1
else :
obsMod = views [ 0 ]
infMod = views [ 1 ]
scObs = [ None ] * len ( obsMod )
for i in range ( 0 , len ( obsMod ) ) :
# WARNING: the [0] in the end assumes that the ARD kernel (if there's addition) is the 1st one
scObs [ i ] = np . atleast_2d ( self . bgplvms [ obsMod [ i ] ] . kern . input_sensitivity ( summarize = False ) ) [ 0 ]
# Normalise to have max 1
scObs [ i ] / = np . max ( scObs [ i ] )
scInf = np . atleast_2d ( self . bgplvms [ infMod ] . kern . input_sensitivity ( summarize = False ) ) [ 0 ]
scInf / = np . max ( scInf )
retainedScales = [ None ] * ( len ( obsMod ) + 1 )
for i in range ( 0 , len ( obsMod ) ) :
retainedScales [ obsMod [ i ] ] = np . where ( scObs [ i ] > threshold ) [ 0 ]
retainedScales [ infMod ] = np . where ( scInf > threshold ) [ 0 ]
for i in range ( len ( retainedScales ) ) :
retainedScales [ i ] = [ k for k in retainedScales [ i ] ] # Transform array to list
sharedDims = set ( retainedScales [ obsMod [ 0 ] ] ) . intersection ( set ( retainedScales [ infMod ] ) )
for i in range ( 1 , len ( obsMod ) ) :
sharedDims = sharedDims . intersection ( set ( retainedScales [ obsMod [ i ] ] ) )
privateDims = [ None ] * M
for i in range ( 0 , len ( retainedScales ) ) :
privateDims [ i ] = set ( retainedScales [ i ] ) . difference ( sharedDims )
privateDims [ i ] = [ k for k in privateDims [ i ] ] # Transform set to list
sharedDims = [ k for k in sharedDims ] # Transform set to list
sharedDims . sort ( )
for i in range ( len ( privateDims ) ) :
privateDims [ i ] . sort ( )
if printOut :
2015-10-01 16:28:42 +01:00
print ( ' # Shared dimensions: ' + str ( sharedDims ) )
2015-10-01 13:07:57 +01:00
for i in range ( len ( retainedScales ) ) :
2015-10-01 16:28:42 +01:00
print ( ' # Private dimensions model ' + str ( i ) + ' : ' + str ( privateDims [ i ] ) )
2015-10-01 13:07:57 +01:00
return sharedDims , privateDims