mirror of
https://github.com/SheffieldML/GPy.git
synced 2026-05-18 13:55:14 +02:00
Merge branch 'params' of github.com:SheffieldML/GPy into params
This commit is contained in:
commit
cdfe7b6bde
5 changed files with 588 additions and 4 deletions
|
|
@ -9,7 +9,10 @@ from parameterized import Parameterized
|
||||||
from param import Param
|
from param import Param
|
||||||
from transformations import Logexp
|
from transformations import Logexp
|
||||||
|
|
||||||
class VariationalPrior(object):
|
class VariationalPrior(Parameterized):
|
||||||
|
def __init__(self, name=None, **kw):
|
||||||
|
super(VariationalPrior, self).__init__(name=name, **kw)
|
||||||
|
|
||||||
def KL_divergence(self, variational_posterior):
|
def KL_divergence(self, variational_posterior):
|
||||||
raise NotImplementedError, "override this for variational inference of latent space"
|
raise NotImplementedError, "override this for variational inference of latent space"
|
||||||
|
|
||||||
|
|
@ -30,6 +33,32 @@ class NormalPrior(VariationalPrior):
|
||||||
variational_posterior.mean.gradient -= variational_posterior.mean
|
variational_posterior.mean.gradient -= variational_posterior.mean
|
||||||
variational_posterior.variance.gradient -= (1. - (1. / (variational_posterior.variance))) * 0.5
|
variational_posterior.variance.gradient -= (1. - (1. / (variational_posterior.variance))) * 0.5
|
||||||
|
|
||||||
|
class SpikeAndSlabPrior(VariationalPrior):
|
||||||
|
def __init__(self, variance = 1.0, pi = 0.5, name='SpikeAndSlabPrior', **kw):
|
||||||
|
super(VariationalPrior, self).__init__(name=name, **kw)
|
||||||
|
assert variance==1.0, "Not Implemented!"
|
||||||
|
self.pi = Param('pi', pi)
|
||||||
|
self.variance = Param('variance',variance)
|
||||||
|
self.add_parameters(self.pi, self.variance)
|
||||||
|
|
||||||
|
def KL_divergence(self, variational_posterior):
|
||||||
|
mu = variational_posterior.mean
|
||||||
|
S = variational_posterior.variance
|
||||||
|
gamma = variational_posterior.binary_prob
|
||||||
|
var_mean = np.square(mu)
|
||||||
|
var_S = (S - np.log(S))
|
||||||
|
var_gamma = (gamma*np.log(gamma/self.pi)).sum()+((1-gamma)*np.log((1-gamma)/(1-self.pi))).sum()
|
||||||
|
return var_gamma+ 0.5 * (gamma* (var_mean + var_S -1)).sum()
|
||||||
|
|
||||||
|
def update_gradients_KL(self, variational_posterior):
|
||||||
|
mu = variational_posterior.mean
|
||||||
|
S = variational_posterior.variance
|
||||||
|
gamma = variational_posterior.binary_prob
|
||||||
|
|
||||||
|
gamma.gradient -= np.log((1-self.pi)/self.pi*gamma/(1.-gamma))+(np.square(mu)+S-np.log(S)-1.)/2.
|
||||||
|
mu.gradient -= gamma*mu
|
||||||
|
S.gradient -= (1. - (1. / (S))) * gamma /2.
|
||||||
|
|
||||||
|
|
||||||
class VariationalPosterior(Parameterized):
|
class VariationalPosterior(Parameterized):
|
||||||
def __init__(self, means=None, variances=None, name=None, **kw):
|
def __init__(self, means=None, variances=None, name=None, **kw):
|
||||||
|
|
@ -65,7 +94,7 @@ class NormalPosterior(VariationalPosterior):
|
||||||
from ...plotting.matplot_dep import variational_plots
|
from ...plotting.matplot_dep import variational_plots
|
||||||
return variational_plots.plot(self,*args)
|
return variational_plots.plot(self,*args)
|
||||||
|
|
||||||
class SpikeAndSlab(VariationalPosterior):
|
class SpikeAndSlabPosterior(VariationalPosterior):
|
||||||
'''
|
'''
|
||||||
The SpikeAndSlab distribution for variational approximations.
|
The SpikeAndSlab distribution for variational approximations.
|
||||||
'''
|
'''
|
||||||
|
|
@ -73,7 +102,7 @@ class SpikeAndSlab(VariationalPosterior):
|
||||||
"""
|
"""
|
||||||
binary_prob : the probability of the distribution on the slab part.
|
binary_prob : the probability of the distribution on the slab part.
|
||||||
"""
|
"""
|
||||||
super(SpikeAndSlab, self).__init__(means, variances, name)
|
super(SpikeAndSlabPosterior, self).__init__(means, variances, name)
|
||||||
self.gamma = Param("binary_prob",binary_prob,)
|
self.gamma = Param("binary_prob",binary_prob,)
|
||||||
self.add_parameter(self.gamma)
|
self.add_parameter(self.gamma)
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -9,3 +9,4 @@ from _src.mlp import MLP
|
||||||
from _src.periodic import PeriodicExponential, PeriodicMatern32, PeriodicMatern52
|
from _src.periodic import PeriodicExponential, PeriodicMatern32, PeriodicMatern52
|
||||||
from _src.independent_outputs import IndependentOutputs, Hierarchical
|
from _src.independent_outputs import IndependentOutputs, Hierarchical
|
||||||
from _src.coregionalize import Coregionalize
|
from _src.coregionalize import Coregionalize
|
||||||
|
from _src.ssrbf import SSRBF
|
||||||
|
|
|
||||||
252
GPy/kern/_src/ssrbf.py
Normal file
252
GPy/kern/_src/ssrbf.py
Normal file
|
|
@ -0,0 +1,252 @@
|
||||||
|
# Copyright (c) 2012, GPy authors (see AUTHORS.txt).
|
||||||
|
# Licensed under the BSD 3-clause license (see LICENSE.txt)
|
||||||
|
|
||||||
|
|
||||||
|
from kern import Kern
|
||||||
|
import numpy as np
|
||||||
|
from ...util.linalg import tdot
|
||||||
|
from ...util.config import *
|
||||||
|
from ...util.caching import cache_this
|
||||||
|
from stationary import Stationary
|
||||||
|
|
||||||
|
class SSRBF(Stationary):
|
||||||
|
"""
|
||||||
|
Radial Basis Function kernel, aka squared-exponential, exponentiated quadratic or Gaussian kernel
|
||||||
|
for Spike-and-Slab GPLVM
|
||||||
|
|
||||||
|
.. math::
|
||||||
|
|
||||||
|
k(r) = \sigma^2 \exp \\bigg(- \\frac{1}{2} r^2 \\bigg) \ \ \ \ \ \\text{ where } r^2 = \sum_{i=1}^d \\frac{ (x_i-x^\prime_i)^2}{\ell_i^2}
|
||||||
|
|
||||||
|
where \ell_i is the lengthscale, \sigma^2 the variance and d the dimensionality of the input.
|
||||||
|
|
||||||
|
:param input_dim: the number of input dimensions
|
||||||
|
:type input_dim: int
|
||||||
|
:param variance: the variance of the kernel
|
||||||
|
:type variance: float
|
||||||
|
:param lengthscale: the vector of lengthscale of the kernel
|
||||||
|
:type lengthscale: array or list of the appropriate size (or float if there is only one lengthscale parameter)
|
||||||
|
:param ARD: Auto Relevance Determination. If equal to "False", the kernel is isotropic (ie. one single lengthscale parameter \ell), otherwise there is one lengthscale parameter per dimension.
|
||||||
|
:type ARD: Boolean
|
||||||
|
:rtype: kernel object
|
||||||
|
|
||||||
|
.. Note: this object implements both the ARD and 'spherical' version of the function
|
||||||
|
"""
|
||||||
|
|
||||||
|
def __init__(self, input_dim, variance=1., lengthscale=None, ARD=True, name='SSRBF'):
|
||||||
|
assert ARD==True, "Not Implemented!"
|
||||||
|
super(SSRBF, self).__init__(input_dim, variance, lengthscale, ARD, name)
|
||||||
|
|
||||||
|
def K_of_r(self, r):
|
||||||
|
return self.variance * np.exp(-0.5 * r**2)
|
||||||
|
|
||||||
|
def dK_dr(self, r):
|
||||||
|
return -r*self.K_of_r(r)
|
||||||
|
|
||||||
|
def parameters_changed(self):
|
||||||
|
pass
|
||||||
|
|
||||||
|
def Kdiag(self, X):
|
||||||
|
ret = np.empty(X.shape[0])
|
||||||
|
ret[:] = self.variance
|
||||||
|
return ret
|
||||||
|
|
||||||
|
#---------------------------------------#
|
||||||
|
# PSI statistics #
|
||||||
|
#---------------------------------------#
|
||||||
|
|
||||||
|
def psi0(self, Z, posterior_variational):
|
||||||
|
ret = np.empty(posterior_variational.mean.shape[0])
|
||||||
|
ret[:] = self.variance
|
||||||
|
return ret
|
||||||
|
|
||||||
|
def psi1(self, Z, posterior_variational):
|
||||||
|
self._psi_computations(Z, posterior_variational.mean, posterior_variational.variance, posterior_variational.binary_prob)
|
||||||
|
return self._psi1
|
||||||
|
|
||||||
|
def psi2(self, Z, posterior_variational):
|
||||||
|
self._psi_computations(Z, posterior_variational.mean, posterior_variational.variance, posterior_variational.binary_prob)
|
||||||
|
return self._psi2
|
||||||
|
|
||||||
|
def dL_dpsi0_dmuSgamma(self, dL_dpsi0, Z, mu, S, gamma, target_mu, target_S, target_gamma):
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
def dL_dpsi1_dmuSgamma(self, dL_dpsi1, Z, mu, S, gamma, target_mu, target_S, target_gamma):
|
||||||
|
self._psi_computations(Z, mu, S, gamma)
|
||||||
|
target_mu += (dL_dpsi1[:, :, None] * self._dpsi1_dmu).sum(axis=1)
|
||||||
|
target_S += (dL_dpsi1[:, :, None] * self._dpsi1_dS).sum(axis=1)
|
||||||
|
target_gamma += (dL_dpsi1[:,:,None] * self._dpsi1_dgamma).sum(axis=1)
|
||||||
|
|
||||||
|
|
||||||
|
def dL_dpsi2_dmuSgamma(self, dL_dpsi2, Z, mu, S, gamma, target_mu, target_S, target_gamma):
|
||||||
|
"""Think N,num_inducing,num_inducing,input_dim """
|
||||||
|
self._psi_computations(Z, mu, S, gamma)
|
||||||
|
target_mu += (dL_dpsi2[:, :, :, None] * self._dpsi2_dmu).reshape(mu.shape[0],-1,mu.shape[1]).sum(axis=1)
|
||||||
|
target_S += (dL_dpsi2[:, :, :, None] * self._dpsi2_dS).reshape(S.shape[0],-1,S.shape[1]).sum(axis=1)
|
||||||
|
target_gamma += (dL_dpsi2[:,:,:, None] *self._dpsi2_dgamma).reshape(gamma.shape[0],-1,gamma.shape[1]).sum(axis=1)
|
||||||
|
|
||||||
|
def update_gradients_variational(self, dL_dKmm, dL_dpsi0, dL_dpsi1, dL_dpsi2, Z, posterior_variational):
|
||||||
|
self._psi_computations(Z, posterior_variational.mean, posterior_variational.variance, posterior_variational.binary_prob)
|
||||||
|
|
||||||
|
#contributions from psi0:
|
||||||
|
self.variance.gradient = np.sum(dL_dpsi0)
|
||||||
|
|
||||||
|
#from psi1
|
||||||
|
self.variance.gradient += np.sum(dL_dpsi1 * self._dpsi1_dvariance)
|
||||||
|
self.lengthscale.gradient = (dL_dpsi1[:,:,None]*self._dpsi1_dlengthscale).reshape(-1,self.input_dim).sum(axis=0)
|
||||||
|
|
||||||
|
|
||||||
|
#from psi2
|
||||||
|
self.variance.gradient += (dL_dpsi2 * self._dpsi2_dvariance).sum()
|
||||||
|
self.lengthscale.gradient += (dL_dpsi2[:,:,:,None] * self._dpsi2_dlengthscale).reshape(-1,self.input_dim).sum(axis=0)
|
||||||
|
|
||||||
|
#from Kmm
|
||||||
|
self._K_computations(Z, None)
|
||||||
|
dvardLdK = self._K_dvar * dL_dKmm
|
||||||
|
var_len3 = self.variance / (np.square(self.lengthscale)*self.lengthscale)
|
||||||
|
|
||||||
|
self.variance.gradient += np.sum(dvardLdK)
|
||||||
|
self.lengthscale.gradient += (np.square(Z[:,None,:]-Z[None,:,:])*dvardLdK[:,:,None]).reshape(-1,self.input_dim).sum(axis=0)*var_len3
|
||||||
|
|
||||||
|
|
||||||
|
def gradients_Z_variational(self, dL_dKmm, dL_dpsi0, dL_dpsi1, dL_dpsi2, Z, posterior_variational):
|
||||||
|
self._psi_computations(Z, posterior_variational.mean, posterior_variational.variance, posterior_variational.binary_prob)
|
||||||
|
|
||||||
|
#psi1
|
||||||
|
grad = (dL_dpsi1[:, :, None] * self._dpsi1_dZ).sum(axis=0)
|
||||||
|
|
||||||
|
#psi2
|
||||||
|
grad += (dL_dpsi2[:, :, :, None] * self._dpsi2_dZ).sum(axis=0).sum(axis=1)
|
||||||
|
|
||||||
|
grad += self.gradients_X(dL_dKmm, Z, None)
|
||||||
|
|
||||||
|
return grad
|
||||||
|
|
||||||
|
def gradients_q_variational(self, dL_dKmm, dL_dpsi0, dL_dpsi1, dL_dpsi2, Z, posterior_variational):
|
||||||
|
ndata = posterior_variational.mean.shape[0]
|
||||||
|
self._psi_computations(Z, posterior_variational.mean, posterior_variational.variance, posterior_variational.binary_prob)
|
||||||
|
#psi1
|
||||||
|
grad_mu = (dL_dpsi1[:, :, None] * self._dpsi1_dmu).sum(axis=1)
|
||||||
|
grad_S = (dL_dpsi1[:, :, None] * self._dpsi1_dS).sum(axis=1)
|
||||||
|
grad_gamma = (dL_dpsi1[:,:,None] * self._dpsi1_dgamma).sum(axis=1)
|
||||||
|
#psi2
|
||||||
|
grad_mu += (dL_dpsi2[:, :, :, None] * self._dpsi2_dmu).reshape(ndata,-1,self.input_dim).sum(axis=1)
|
||||||
|
grad_S += (dL_dpsi2[:, :, :, None] * self._dpsi2_dS).reshape(ndata,-1,self.input_dim).sum(axis=1)
|
||||||
|
grad_gamma += (dL_dpsi2[:,:,:, None] *self._dpsi2_dgamma).reshape(ndata,-1,self.input_dim).sum(axis=1)
|
||||||
|
|
||||||
|
return grad_mu, grad_S, grad_gamma
|
||||||
|
|
||||||
|
def gradients_X(self, dL_dK, X, X2=None):
|
||||||
|
#if self._X is None or X.base is not self._X.base or X2 is not None:
|
||||||
|
if X2==None:
|
||||||
|
_K_dist = X[:,None,:] - X[None,:,:]
|
||||||
|
_K_dist2 = np.square(_K_dist/self.lengthscale).sum(axis=-1)
|
||||||
|
dK_dX = self.variance*np.exp(-0.5 * self._K_dist2[:,:,None]) * (-2.*_K_dist/np.square(self.lengthscale))
|
||||||
|
dL_dX = (dL_dK[:,:,None] * dK_dX).sum(axis=1)
|
||||||
|
else:
|
||||||
|
_K_dist = X[:,None,:] - X2[None,:,:]
|
||||||
|
_K_dist2 = np.square(_K_dist/self.lengthscale).sum(axis=-1)
|
||||||
|
dK_dX = self.variance*np.exp(-0.5 * self._K_dist2[:,:,None]) * (-_K_dist/np.square(self.lengthscale))
|
||||||
|
dL_dX = (dL_dK[:,:,None] * dK_dX).sum(axis=1)
|
||||||
|
return dL_dX
|
||||||
|
|
||||||
|
#---------------------------------------#
|
||||||
|
# Precomputations #
|
||||||
|
#---------------------------------------#
|
||||||
|
|
||||||
|
@cache_this(1)
|
||||||
|
def _K_computations(self, X, X2):
|
||||||
|
"""
|
||||||
|
K(X,X2) - X is NxQ
|
||||||
|
Q -> input dimension (self.input_dim)
|
||||||
|
"""
|
||||||
|
if X2 is None:
|
||||||
|
self._X2 = None
|
||||||
|
|
||||||
|
X = X / self.lengthscale
|
||||||
|
Xsquare = np.sum(np.square(X), axis=1)
|
||||||
|
self._K_dist2 = -2.*tdot(X) + (Xsquare[:, None] + Xsquare[None, :])
|
||||||
|
else:
|
||||||
|
self._X2 = X2.copy()
|
||||||
|
|
||||||
|
X = X / self.lengthscale
|
||||||
|
X2 = X2 / self.lengthscale
|
||||||
|
self._K_dist2 = -2.*np.dot(X, X2.T) + (np.sum(np.square(X), axis=1)[:, None] + np.sum(np.square(X2), axis=1)[None, :])
|
||||||
|
self._K_dvar = np.exp(-0.5 * self._K_dist2)
|
||||||
|
|
||||||
|
@cache_this(1)
|
||||||
|
def _psi_computations(self, Z, mu, S, gamma):
|
||||||
|
"""
|
||||||
|
Z - MxQ
|
||||||
|
mu - NxQ
|
||||||
|
S - NxQ
|
||||||
|
gamma - NxQ
|
||||||
|
"""
|
||||||
|
# here are the "statistics" for psi1 and psi2
|
||||||
|
# Produced intermediate results:
|
||||||
|
# _psi1 NxM
|
||||||
|
# _dpsi1_dvariance NxM
|
||||||
|
# _dpsi1_dlengthscale NxMxQ
|
||||||
|
# _dpsi1_dZ NxMxQ
|
||||||
|
# _dpsi1_dgamma NxMxQ
|
||||||
|
# _dpsi1_dmu NxMxQ
|
||||||
|
# _dpsi1_dS NxMxQ
|
||||||
|
# _psi2 NxMxM
|
||||||
|
# _psi2_dvariance NxMxM
|
||||||
|
# _psi2_dlengthscale NxMxMxQ
|
||||||
|
# _psi2_dZ NxMxMxQ
|
||||||
|
# _psi2_dgamma NxMxMxQ
|
||||||
|
# _psi2_dmu NxMxMxQ
|
||||||
|
# _psi2_dS NxMxMxQ
|
||||||
|
|
||||||
|
lengthscale2 = np.square(self.lengthscale)
|
||||||
|
|
||||||
|
_psi2_Zhat = 0.5 * (Z[:, None, :] + Z[None, :, :]) # M,M,Q
|
||||||
|
_psi2_Zdist = 0.5 * (Z[:, None, :] - Z[None, :, :]) # M,M,Q
|
||||||
|
_psi2_Zdist_sq = np.square(_psi2_Zdist / self.lengthscale) # M,M,Q
|
||||||
|
_psi2_Z_sq_sum = (np.square(Z[:,None,:])+np.square(Z[None,:,:]))/lengthscale2 # MxMxQ
|
||||||
|
|
||||||
|
# psi1
|
||||||
|
_psi1_denom = S[:, None, :] / lengthscale2 + 1. # Nx1xQ
|
||||||
|
_psi1_denom_sqrt = np.sqrt(_psi1_denom) #Nx1xQ
|
||||||
|
_psi1_dist = Z[None, :, :] - mu[:, None, :] # NxMxQ
|
||||||
|
_psi1_dist_sq = np.square(_psi1_dist) / (lengthscale2 * _psi1_denom) # NxMxQ
|
||||||
|
_psi1_common = gamma[:,None,:] / (lengthscale2*_psi1_denom*_psi1_denom_sqrt) #Nx1xQ
|
||||||
|
_psi1_exponent1 = np.log(gamma[:,None,:]) -0.5 * (_psi1_dist_sq + np.log(_psi1_denom)) # NxMxQ
|
||||||
|
_psi1_exponent2 = np.log(1.-gamma[:,None,:]) -0.5 * (np.square(Z[None,:,:])/lengthscale2) # NxMxQ
|
||||||
|
_psi1_exponent = np.log(np.exp(_psi1_exponent1) + np.exp(_psi1_exponent2)) #NxMxQ
|
||||||
|
_psi1_exp_sum = _psi1_exponent.sum(axis=-1) #NxM
|
||||||
|
_psi1_exp_dist_sq = np.exp(-0.5*_psi1_dist_sq) # NxMxQ
|
||||||
|
_psi1_exp_Z = np.exp(-0.5*np.square(Z[None,:,:])/lengthscale2) # 1xMxQ
|
||||||
|
_psi1_q = self.variance * np.exp(_psi1_exp_sum[:,:,None] - _psi1_exponent) # NxMxQ
|
||||||
|
self._psi1 = self.variance * np.exp(_psi1_exp_sum) # NxM
|
||||||
|
self._dpsi1_dvariance = self._psi1 / self.variance # NxM
|
||||||
|
self._dpsi1_dgamma = _psi1_q * (_psi1_exp_dist_sq/_psi1_denom_sqrt-_psi1_exp_Z) # NxMxQ
|
||||||
|
self._dpsi1_dmu = _psi1_q * (_psi1_exp_dist_sq * _psi1_dist * _psi1_common) # NxMxQ
|
||||||
|
self._dpsi1_dS = _psi1_q * (_psi1_exp_dist_sq * _psi1_common * 0.5 * (_psi1_dist_sq - 1.)) # NxMxQ
|
||||||
|
self._dpsi1_dZ = _psi1_q * (- _psi1_common * _psi1_dist * _psi1_exp_dist_sq - (1-gamma[:,None,:])/lengthscale2*Z[None,:,:]*_psi1_exp_Z) # NxMxQ
|
||||||
|
self._dpsi1_dlengthscale = 2.*self.lengthscale*_psi1_q * (0.5*_psi1_common*(S[:,None,:]/lengthscale2+_psi1_dist_sq)*_psi1_exp_dist_sq + 0.5*(1-gamma[:,None,:])*np.square(Z[None,:,:]/lengthscale2)*_psi1_exp_Z) # NxMxQ
|
||||||
|
|
||||||
|
|
||||||
|
# psi2
|
||||||
|
_psi2_denom = 2.*S[:, None, None, :] / lengthscale2 + 1. # Nx1x1xQ
|
||||||
|
_psi2_denom_sqrt = np.sqrt(_psi2_denom)
|
||||||
|
_psi2_mudist = mu[:,None,None,:]-_psi2_Zhat #N,M,M,Q
|
||||||
|
_psi2_mudist_sq = np.square(_psi2_mudist)/(lengthscale2*_psi2_denom)
|
||||||
|
_psi2_common = gamma[:,None,None,:]/(lengthscale2 * _psi2_denom * _psi2_denom_sqrt) # Nx1x1xQ
|
||||||
|
_psi2_exponent1 = -_psi2_Zdist_sq -_psi2_mudist_sq -0.5*np.log(_psi2_denom)+np.log(gamma[:,None,None,:]) #N,M,M,Q
|
||||||
|
_psi2_exponent2 = np.log(1.-gamma[:,None,None,:]) - 0.5*(_psi2_Z_sq_sum) # NxMxMxQ
|
||||||
|
_psi2_exponent = np.log(np.exp(_psi2_exponent1) + np.exp(_psi2_exponent2))
|
||||||
|
_psi2_exp_sum = _psi2_exponent.sum(axis=-1) #NxM
|
||||||
|
_psi2_q = np.square(self.variance) * np.exp(_psi2_exp_sum[:,:,:,None]-_psi2_exponent) # NxMxMxQ
|
||||||
|
_psi2_exp_dist_sq = np.exp(-_psi2_Zdist_sq -_psi2_mudist_sq) # NxMxMxQ
|
||||||
|
_psi2_exp_Z = np.exp(-0.5*_psi2_Z_sq_sum) # MxMxQ
|
||||||
|
self._psi2 = np.square(self.variance) * np.exp(_psi2_exp_sum) # N,M,M
|
||||||
|
self._dpsi2_dvariance = 2. * self._psi2/self.variance # NxMxM
|
||||||
|
self._dpsi2_dgamma = _psi2_q * (_psi2_exp_dist_sq/_psi2_denom_sqrt - _psi2_exp_Z) # NxMxMxQ
|
||||||
|
self._dpsi2_dmu = _psi2_q * (-2.*_psi2_common*_psi2_mudist * _psi2_exp_dist_sq) # NxMxMxQ
|
||||||
|
self._dpsi2_dS = _psi2_q * (_psi2_common * (2.*_psi2_mudist_sq - 1.) * _psi2_exp_dist_sq) # NxMxMxQ
|
||||||
|
self._dpsi2_dZ = 2.*_psi2_q * (_psi2_common*(-_psi2_Zdist*_psi2_denom+_psi2_mudist)*_psi2_exp_dist_sq - (1-gamma[:,None,None,:])*Z[:,None,:]/lengthscale2*_psi2_exp_Z) # NxMxMxQ
|
||||||
|
self._dpsi2_dlengthscale = 2.*self.lengthscale* _psi2_q * (_psi2_common*(S[:,None,None,:]/lengthscale2+_psi2_Zdist_sq*_psi2_denom+_psi2_mudist_sq)*_psi2_exp_dist_sq+(1-gamma[:,None,None,:])*_psi2_Z_sq_sum*0.5/lengthscale2*_psi2_exp_Z) # NxMxMxQ
|
||||||
|
|
||||||
|
|
@ -15,3 +15,4 @@ from mrd import MRD
|
||||||
from gradient_checker import GradientChecker
|
from gradient_checker import GradientChecker
|
||||||
from gp_multioutput_regression import GPMultioutputRegression
|
from gp_multioutput_regression import GPMultioutputRegression
|
||||||
from sparse_gp_multioutput_regression import SparseGPMultioutputRegression
|
from sparse_gp_multioutput_regression import SparseGPMultioutputRegression
|
||||||
|
from ss_gplvm import SSGPLVM
|
||||||
301
GPy/models/ss_gplvm.py
Normal file
301
GPy/models/ss_gplvm.py
Normal file
|
|
@ -0,0 +1,301 @@
|
||||||
|
# Copyright (c) 2012, GPy authors (see AUTHORS.txt).
|
||||||
|
# Licensed under the BSD 3-clause license (see LICENSE.txt)
|
||||||
|
|
||||||
|
import numpy as np
|
||||||
|
import itertools
|
||||||
|
from matplotlib import pyplot
|
||||||
|
|
||||||
|
from ..core.sparse_gp import SparseGP
|
||||||
|
from .. import kern
|
||||||
|
from ..likelihoods import Gaussian
|
||||||
|
from ..inference.optimization import SCG
|
||||||
|
from ..util import linalg
|
||||||
|
from ..core.parameterization.variational import SpikeAndSlabPrior, SpikeAndSlabPosterior
|
||||||
|
|
||||||
|
class SSGPLVM(SparseGP):
|
||||||
|
"""
|
||||||
|
Spike-and-Slab Gaussian Process Latent Variable Model
|
||||||
|
|
||||||
|
:param Y: observed data (np.ndarray) or GPy.likelihood
|
||||||
|
:type Y: np.ndarray| GPy.likelihood instance
|
||||||
|
:param input_dim: latent dimensionality
|
||||||
|
:type input_dim: int
|
||||||
|
:param init: initialisation method for the latent space
|
||||||
|
:type init: 'PCA'|'random'
|
||||||
|
|
||||||
|
"""
|
||||||
|
def __init__(self, Y, input_dim, X=None, X_variance=None, init='PCA', num_inducing=10,
|
||||||
|
Z=None, kernel=None, inference_method=None, likelihood=None, name='Spike-and-Slab GPLVM', **kwargs):
|
||||||
|
|
||||||
|
if X == None: # The mean of variational approximation (mu)
|
||||||
|
from ..util.initialization import initialize_latent
|
||||||
|
X = initialize_latent(init, input_dim, Y)
|
||||||
|
self.init = init
|
||||||
|
|
||||||
|
if X_variance is None: # The variance of the variational approximation (S)
|
||||||
|
X_variance = np.random.uniform(0,.1,X.shape)
|
||||||
|
|
||||||
|
gamma = np.empty_like(X) # The posterior probabilities of the binary variable in the variational approximation
|
||||||
|
gamma[:] = 0.5
|
||||||
|
|
||||||
|
if Z is None:
|
||||||
|
Z = np.random.permutation(X.copy())[:num_inducing]
|
||||||
|
assert Z.shape[1] == X.shape[1]
|
||||||
|
|
||||||
|
if likelihood is None:
|
||||||
|
likelihood = Gaussian()
|
||||||
|
|
||||||
|
if kernel is None:
|
||||||
|
kernel = kern.SSRBF(input_dim)
|
||||||
|
|
||||||
|
self.variational_prior = SpikeAndSlabPrior(pi=0.5) # the prior probability of the latent binary variable b
|
||||||
|
X = SpikeAndSlabPosterior(X, X_variance, gamma)
|
||||||
|
|
||||||
|
SparseGP.__init__(self, X, Y, Z, kernel, likelihood, inference_method, name, **kwargs)
|
||||||
|
self.add_parameter(self.X, index=0)
|
||||||
|
|
||||||
|
def parameters_changed(self):
|
||||||
|
super(SSGPLVM, self).parameters_changed()
|
||||||
|
self._log_marginal_likelihood -= self.variational_prior.KL_divergence(self.X)
|
||||||
|
|
||||||
|
self.X.mean.gradient, self.X.variance.gradient, self.X.binary_prob.gradient = self.kern.gradients_q_variational(posterior_variational=self.X, Z=self.Z, **self.grad_dict)
|
||||||
|
|
||||||
|
# update for the KL divergence
|
||||||
|
self.variational_prior.update_gradients_KL(self.X)
|
||||||
|
|
||||||
|
def plot_latent(self, plot_inducing=True, *args, **kwargs):
|
||||||
|
pass
|
||||||
|
#return plot_latent.plot_latent(self, plot_inducing=plot_inducing, *args, **kwargs)
|
||||||
|
|
||||||
|
def do_test_latents(self, Y):
|
||||||
|
"""
|
||||||
|
Compute the latent representation for a set of new points Y
|
||||||
|
|
||||||
|
Notes:
|
||||||
|
This will only work with a univariate Gaussian likelihood (for now)
|
||||||
|
"""
|
||||||
|
assert not self.likelihood.is_heteroscedastic
|
||||||
|
N_test = Y.shape[0]
|
||||||
|
input_dim = self.Z.shape[1]
|
||||||
|
means = np.zeros((N_test, input_dim))
|
||||||
|
covars = np.zeros((N_test, input_dim))
|
||||||
|
|
||||||
|
dpsi0 = -0.5 * self.output_dim * self.likelihood.precision
|
||||||
|
dpsi2 = self.dL_dpsi2[0][None, :, :] # TODO: this may change if we ignore het. likelihoods
|
||||||
|
V = self.likelihood.precision * Y
|
||||||
|
|
||||||
|
#compute CPsi1V
|
||||||
|
if self.Cpsi1V is None:
|
||||||
|
psi1V = np.dot(self.psi1.T, self.likelihood.V)
|
||||||
|
tmp, _ = linalg.dtrtrs(self._Lm, np.asfortranarray(psi1V), lower=1, trans=0)
|
||||||
|
tmp, _ = linalg.dpotrs(self.LB, tmp, lower=1)
|
||||||
|
self.Cpsi1V, _ = linalg.dtrtrs(self._Lm, tmp, lower=1, trans=1)
|
||||||
|
|
||||||
|
dpsi1 = np.dot(self.Cpsi1V, V.T)
|
||||||
|
|
||||||
|
start = np.zeros(self.input_dim * 2)
|
||||||
|
|
||||||
|
for n, dpsi1_n in enumerate(dpsi1.T[:, :, None]):
|
||||||
|
args = (self.kern, self.Z, dpsi0, dpsi1_n.T, dpsi2)
|
||||||
|
xopt, fopt, neval, status = SCG(f=latent_cost, gradf=latent_grad, x=start, optargs=args, display=False)
|
||||||
|
|
||||||
|
mu, log_S = xopt.reshape(2, 1, -1)
|
||||||
|
means[n] = mu[0].copy()
|
||||||
|
covars[n] = np.exp(log_S[0]).copy()
|
||||||
|
|
||||||
|
return means, covars
|
||||||
|
|
||||||
|
def dmu_dX(self, Xnew):
|
||||||
|
"""
|
||||||
|
Calculate the gradient of the prediction at Xnew w.r.t Xnew.
|
||||||
|
"""
|
||||||
|
dmu_dX = np.zeros_like(Xnew)
|
||||||
|
for i in range(self.Z.shape[0]):
|
||||||
|
dmu_dX += self.kern.dK_dX(self.Cpsi1Vf[i:i + 1, :], Xnew, self.Z[i:i + 1, :])
|
||||||
|
return dmu_dX
|
||||||
|
|
||||||
|
def dmu_dXnew(self, Xnew):
|
||||||
|
"""
|
||||||
|
Individual gradient of prediction at Xnew w.r.t. each sample in Xnew
|
||||||
|
"""
|
||||||
|
dK_dX = np.zeros((Xnew.shape[0], self.num_inducing))
|
||||||
|
ones = np.ones((1, 1))
|
||||||
|
for i in range(self.Z.shape[0]):
|
||||||
|
dK_dX[:, i] = self.kern.dK_dX(ones, Xnew, self.Z[i:i + 1, :]).sum(-1)
|
||||||
|
return np.dot(dK_dX, self.Cpsi1Vf)
|
||||||
|
|
||||||
|
def plot_steepest_gradient_map(self, fignum=None, ax=None, which_indices=None, labels=None, data_labels=None, data_marker='o', data_s=40, resolution=20, aspect='auto', updates=False, ** kwargs):
|
||||||
|
input_1, input_2 = significant_dims = most_significant_input_dimensions(self, which_indices)
|
||||||
|
|
||||||
|
X = np.zeros((resolution ** 2, self.input_dim))
|
||||||
|
indices = np.r_[:X.shape[0]]
|
||||||
|
if labels is None:
|
||||||
|
labels = range(self.output_dim)
|
||||||
|
|
||||||
|
def plot_function(x):
|
||||||
|
X[:, significant_dims] = x
|
||||||
|
dmu_dX = self.dmu_dXnew(X)
|
||||||
|
argmax = np.argmax(dmu_dX, 1)
|
||||||
|
return dmu_dX[indices, argmax], np.array(labels)[argmax]
|
||||||
|
|
||||||
|
if ax is None:
|
||||||
|
fig = pyplot.figure(num=fignum)
|
||||||
|
ax = fig.add_subplot(111)
|
||||||
|
|
||||||
|
if data_labels is None:
|
||||||
|
data_labels = np.ones(self.num_data)
|
||||||
|
ulabels = []
|
||||||
|
for lab in data_labels:
|
||||||
|
if not lab in ulabels:
|
||||||
|
ulabels.append(lab)
|
||||||
|
marker = itertools.cycle(list(data_marker))
|
||||||
|
from GPy.util import Tango
|
||||||
|
for i, ul in enumerate(ulabels):
|
||||||
|
if type(ul) is np.string_:
|
||||||
|
this_label = ul
|
||||||
|
elif type(ul) is np.int64:
|
||||||
|
this_label = 'class %i' % ul
|
||||||
|
else:
|
||||||
|
this_label = 'class %i' % i
|
||||||
|
m = marker.next()
|
||||||
|
index = np.nonzero(data_labels == ul)[0]
|
||||||
|
x = self.X[index, input_1]
|
||||||
|
y = self.X[index, input_2]
|
||||||
|
ax.scatter(x, y, marker=m, s=data_s, color=Tango.nextMedium(), label=this_label)
|
||||||
|
|
||||||
|
ax.set_xlabel('latent dimension %i' % input_1)
|
||||||
|
ax.set_ylabel('latent dimension %i' % input_2)
|
||||||
|
|
||||||
|
from matplotlib.cm import get_cmap
|
||||||
|
from GPy.util.latent_space_visualizations.controllers.imshow_controller import ImAnnotateController
|
||||||
|
if not 'cmap' in kwargs.keys():
|
||||||
|
kwargs.update(cmap=get_cmap('jet'))
|
||||||
|
controller = ImAnnotateController(ax,
|
||||||
|
plot_function,
|
||||||
|
tuple(self.X.min(0)[:, significant_dims]) + tuple(self.X.max(0)[:, significant_dims]),
|
||||||
|
resolution=resolution,
|
||||||
|
aspect=aspect,
|
||||||
|
**kwargs)
|
||||||
|
ax.legend()
|
||||||
|
ax.figure.tight_layout()
|
||||||
|
if updates:
|
||||||
|
pyplot.show()
|
||||||
|
clear = raw_input('Enter to continue')
|
||||||
|
if clear.lower() in 'yes' or clear == '':
|
||||||
|
controller.deactivate()
|
||||||
|
return controller.view
|
||||||
|
|
||||||
|
def plot_X_1d(self, fignum=None, ax=None, colors=None):
|
||||||
|
"""
|
||||||
|
Plot latent space X in 1D:
|
||||||
|
|
||||||
|
- if fig is given, create input_dim subplots in fig and plot in these
|
||||||
|
- if ax is given plot input_dim 1D latent space plots of X into each `axis`
|
||||||
|
- if neither fig nor ax is given create a figure with fignum and plot in there
|
||||||
|
|
||||||
|
colors:
|
||||||
|
colors of different latent space dimensions input_dim
|
||||||
|
|
||||||
|
"""
|
||||||
|
import pylab
|
||||||
|
if ax is None:
|
||||||
|
fig = pylab.figure(num=fignum, figsize=(8, min(12, (2 * self.X.shape[1]))))
|
||||||
|
if colors is None:
|
||||||
|
colors = pylab.gca()._get_lines.color_cycle
|
||||||
|
pylab.clf()
|
||||||
|
else:
|
||||||
|
colors = iter(colors)
|
||||||
|
plots = []
|
||||||
|
x = np.arange(self.X.shape[0])
|
||||||
|
for i in range(self.X.shape[1]):
|
||||||
|
if ax is None:
|
||||||
|
a = fig.add_subplot(self.X.shape[1], 1, i + 1)
|
||||||
|
elif isinstance(ax, (tuple, list)):
|
||||||
|
a = ax[i]
|
||||||
|
else:
|
||||||
|
raise ValueError("Need one ax per latent dimnesion input_dim")
|
||||||
|
a.plot(self.X, c='k', alpha=.3)
|
||||||
|
plots.extend(a.plot(x, self.X.T[i], c=colors.next(), label=r"$\mathbf{{X_{{{}}}}}$".format(i)))
|
||||||
|
a.fill_between(x,
|
||||||
|
self.X.T[i] - 2 * np.sqrt(self.X_variance.T[i]),
|
||||||
|
self.X.T[i] + 2 * np.sqrt(self.X_variance.T[i]),
|
||||||
|
facecolor=plots[-1].get_color(),
|
||||||
|
alpha=.3)
|
||||||
|
a.legend(borderaxespad=0.)
|
||||||
|
a.set_xlim(x.min(), x.max())
|
||||||
|
if i < self.X.shape[1] - 1:
|
||||||
|
a.set_xticklabels('')
|
||||||
|
pylab.draw()
|
||||||
|
if ax is None:
|
||||||
|
fig.tight_layout(h_pad=.01) # , rect=(0, 0, 1, .95))
|
||||||
|
return fig
|
||||||
|
|
||||||
|
def getstate(self):
|
||||||
|
"""
|
||||||
|
Get the current state of the class,
|
||||||
|
here just all the indices, rest can get recomputed
|
||||||
|
"""
|
||||||
|
return SparseGP._getstate(self) + [self.init]
|
||||||
|
|
||||||
|
def setstate(self, state):
|
||||||
|
self._const_jitter = None
|
||||||
|
self.init = state.pop()
|
||||||
|
SparseGP._setstate(self, state)
|
||||||
|
|
||||||
|
|
||||||
|
def latent_cost_and_grad(mu_S, kern, Z, dL_dpsi0, dL_dpsi1, dL_dpsi2):
|
||||||
|
"""
|
||||||
|
objective function for fitting the latent variables for test points
|
||||||
|
(negative log-likelihood: should be minimised!)
|
||||||
|
"""
|
||||||
|
mu, log_S = mu_S.reshape(2, 1, -1)
|
||||||
|
S = np.exp(log_S)
|
||||||
|
|
||||||
|
psi0 = kern.psi0(Z, mu, S)
|
||||||
|
psi1 = kern.psi1(Z, mu, S)
|
||||||
|
psi2 = kern.psi2(Z, mu, S)
|
||||||
|
|
||||||
|
lik = dL_dpsi0 * psi0 + np.dot(dL_dpsi1.flatten(), psi1.flatten()) + np.dot(dL_dpsi2.flatten(), psi2.flatten()) - 0.5 * np.sum(np.square(mu) + S) + 0.5 * np.sum(log_S)
|
||||||
|
|
||||||
|
mu0, S0 = kern.dpsi0_dmuS(dL_dpsi0, Z, mu, S)
|
||||||
|
mu1, S1 = kern.dpsi1_dmuS(dL_dpsi1, Z, mu, S)
|
||||||
|
mu2, S2 = kern.dpsi2_dmuS(dL_dpsi2, Z, mu, S)
|
||||||
|
|
||||||
|
dmu = mu0 + mu1 + mu2 - mu
|
||||||
|
# dS = S0 + S1 + S2 -0.5 + .5/S
|
||||||
|
dlnS = S * (S0 + S1 + S2 - 0.5) + .5
|
||||||
|
return -lik, -np.hstack((dmu.flatten(), dlnS.flatten()))
|
||||||
|
|
||||||
|
def latent_cost(mu_S, kern, Z, dL_dpsi0, dL_dpsi1, dL_dpsi2):
|
||||||
|
"""
|
||||||
|
objective function for fitting the latent variables (negative log-likelihood: should be minimised!)
|
||||||
|
This is the same as latent_cost_and_grad but only for the objective
|
||||||
|
"""
|
||||||
|
mu, log_S = mu_S.reshape(2, 1, -1)
|
||||||
|
S = np.exp(log_S)
|
||||||
|
|
||||||
|
psi0 = kern.psi0(Z, mu, S)
|
||||||
|
psi1 = kern.psi1(Z, mu, S)
|
||||||
|
psi2 = kern.psi2(Z, mu, S)
|
||||||
|
|
||||||
|
lik = dL_dpsi0 * psi0 + np.dot(dL_dpsi1.flatten(), psi1.flatten()) + np.dot(dL_dpsi2.flatten(), psi2.flatten()) - 0.5 * np.sum(np.square(mu) + S) + 0.5 * np.sum(log_S)
|
||||||
|
return -float(lik)
|
||||||
|
|
||||||
|
def latent_grad(mu_S, kern, Z, dL_dpsi0, dL_dpsi1, dL_dpsi2):
|
||||||
|
"""
|
||||||
|
This is the same as latent_cost_and_grad but only for the grad
|
||||||
|
"""
|
||||||
|
mu, log_S = mu_S.reshape(2, 1, -1)
|
||||||
|
S = np.exp(log_S)
|
||||||
|
|
||||||
|
mu0, S0 = kern.dpsi0_dmuS(dL_dpsi0, Z, mu, S)
|
||||||
|
mu1, S1 = kern.dpsi1_dmuS(dL_dpsi1, Z, mu, S)
|
||||||
|
mu2, S2 = kern.dpsi2_dmuS(dL_dpsi2, Z, mu, S)
|
||||||
|
|
||||||
|
dmu = mu0 + mu1 + mu2 - mu
|
||||||
|
# dS = S0 + S1 + S2 -0.5 + .5/S
|
||||||
|
dlnS = S * (S0 + S1 + S2 - 0.5) + .5
|
||||||
|
|
||||||
|
return -np.hstack((dmu.flatten(), dlnS.flatten()))
|
||||||
|
|
||||||
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue